dissect.volume 3.12.dev3__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 (136) hide show
  1. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/.gitattributes +1 -0
  2. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/PKG-INFO +1 -1
  3. dissect_volume-3.13.dev1/dissect/volume/vinum/c_vinum.py +83 -0
  4. dissect_volume-3.13.dev1/dissect/volume/vinum/config.py +425 -0
  5. dissect_volume-3.13.dev1/dissect/volume/vinum/vinum.py +349 -0
  6. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/PKG-INFO +1 -1
  7. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/SOURCES.txt +19 -0
  8. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/conftest.py +54 -0
  9. dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diska.bin.gz +0 -0
  10. dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diskb.bin.gz +0 -0
  11. dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diska.bin.gz +0 -0
  12. dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diskb.bin.gz +0 -0
  13. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diska.bin.gz +0 -0
  14. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskb.bin.gz +0 -0
  15. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskc.bin.gz +0 -0
  16. dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskd.bin.gz +0 -0
  17. dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diska.bin.gz +0 -0
  18. dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diskb.bin.gz +0 -0
  19. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diska.bin.gz +0 -0
  20. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskb.bin.gz +0 -0
  21. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskc.bin.gz +0 -0
  22. dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskd.bin.gz +0 -0
  23. dissect_volume-3.13.dev1/tests/test_vinum.py +238 -0
  24. dissect_volume-3.13.dev1/tests/test_vinum_config.py +460 -0
  25. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/COPYRIGHT +0 -0
  26. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/LICENSE +0 -0
  27. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/MANIFEST.in +0 -0
  28. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/README.md +0 -0
  29. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/__init__.py +0 -0
  30. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/__init__.py +0 -0
  31. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/c_ddf.py +0 -0
  32. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/ddf.py +0 -0
  33. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/__init__.py +0 -0
  34. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/disk.py +0 -0
  35. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/partition.py +0 -0
  36. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/__init__.py +0 -0
  37. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/apm.py +0 -0
  38. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/bsd.py +0 -0
  39. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/gpt.py +0 -0
  40. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/mbr.py +0 -0
  41. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/__init__.py +0 -0
  42. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/btree.py +0 -0
  43. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/c_dm.py +0 -0
  44. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/thin.py +0 -0
  45. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/exceptions.py +0 -0
  46. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ldm.py +0 -0
  47. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/__init__.py +0 -0
  48. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/c_lvm2.py +0 -0
  49. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/lvm2.py +0 -0
  50. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/metadata.py +0 -0
  51. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/physical.py +0 -0
  52. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/__init__.py +0 -0
  53. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/c_md.py +0 -0
  54. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/md.py +0 -0
  55. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/__init__.py +0 -0
  56. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/raid.py +0 -0
  57. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/stream.py +0 -0
  58. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/vss.py +0 -0
  59. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/dependency_links.txt +0 -0
  60. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/requires.txt +0 -0
  61. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/top_level.txt +0 -0
  62. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/pyproject.toml +0 -0
  63. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/setup.cfg +0 -0
  64. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/__init__.py +0 -0
  65. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/apm.bin +0 -0
  66. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/bsd.bin +0 -0
  67. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/bsd64.bin.gz +0 -0
  68. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-1.bin.gz +0 -0
  69. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-2.bin.gz +0 -0
  70. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-3.bin.gz +0 -0
  71. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-1.bin.gz +0 -0
  72. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-2.bin.gz +0 -0
  73. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-1.bin.gz +0 -0
  74. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-2.bin.gz +0 -0
  75. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-1.bin.gz +0 -0
  76. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-2.bin.gz +0 -0
  77. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-3.bin.gz +0 -0
  78. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-4.bin.gz +0 -0
  79. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-1.bin.gz +0 -0
  80. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-2.bin.gz +0 -0
  81. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-1.bin.gz +0 -0
  82. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-2.bin.gz +0 -0
  83. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-3.bin.gz +0 -0
  84. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-1.bin.gz +0 -0
  85. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-2.bin.gz +0 -0
  86. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-3.bin.gz +0 -0
  87. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-4.bin.gz +0 -0
  88. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-data.bin.gz +0 -0
  89. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-data.bin.gz +0 -0
  90. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-metadata.bin.gz +0 -0
  91. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-metadata.bin.gz +0 -0
  92. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt.bin +0 -0
  93. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_4k.bin +0 -0
  94. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_esxi.bin +0 -0
  95. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_hybrid.bin +0 -0
  96. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_no_name_xff.bin +0 -0
  97. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-1.bin.gz +0 -0
  98. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-2.bin.gz +0 -0
  99. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-thin.bin.gz +0 -0
  100. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm.bin.gz +0 -0
  101. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/mbr.bin +0 -0
  102. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-1.bin.gz +0 -0
  103. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-2.bin.gz +0 -0
  104. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-1.bin.gz +0 -0
  105. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-2.bin.gz +0 -0
  106. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-1.bin.gz +0 -0
  107. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-2.bin.gz +0 -0
  108. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-1.bin.gz +0 -0
  109. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-2.bin.gz +0 -0
  110. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-3.bin.gz +0 -0
  111. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-1.bin.gz +0 -0
  112. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-2.bin.gz +0 -0
  113. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-1.bin.gz +0 -0
  114. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-2.bin.gz +0 -0
  115. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-1.bin.gz +0 -0
  116. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-2.bin.gz +0 -0
  117. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-3.bin.gz +0 -0
  118. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-1.bin.gz +0 -0
  119. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-2.bin.gz +0 -0
  120. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-3.bin.gz +0 -0
  121. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-1.bin.gz +0 -0
  122. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-2.bin.gz +0 -0
  123. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-3.bin.gz +0 -0
  124. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-4.bin.gz +0 -0
  125. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/Makefile +0 -0
  126. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/conf.py +0 -0
  127. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/index.rst +0 -0
  128. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_apm.py +0 -0
  129. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_bsd.py +0 -0
  130. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_ddf.py +0 -0
  131. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_dm.py +0 -0
  132. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_gpt.py +0 -0
  133. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_lvm.py +0 -0
  134. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_mbr.py +0 -0
  135. {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_md.py +0 -0
  136. {dissect_volume-3.12.dev3 → 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.dev3
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
@@ -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}
@@ -0,0 +1,425 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import re
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from enum import Enum, auto
9
+ from typing import TypedDict
10
+
11
+ from dissect.volume.vinum.c_vinum import c_vinum
12
+
13
+ log = logging.getLogger(__name__)
14
+ log.setLevel(os.getenv("DISSECT_LOG_VINUM", "CRITICAL"))
15
+
16
+
17
+ @dataclass
18
+ class Volume:
19
+ """The representation of a Vinum Volume.
20
+
21
+ A Vinum Volume defines a single RAID set. One or more Vinum Plexes can be
22
+ part of a Volume.
23
+ """
24
+
25
+ timestamp: datetime
26
+ name: bytes
27
+ state: VolumeState | None = None
28
+
29
+
30
+ @dataclass
31
+ class Plex:
32
+ """The representation of a Vinum Plex.
33
+
34
+ A Vinum Plex can be thought of as one of the individual disks in a mirrored
35
+ array. One or more Vinum SDs can be part of a Plex. The Plex defines the
36
+ type of RAID in which these SDs are organized.
37
+ """
38
+
39
+ timestamp: datetime
40
+ name: bytes | None = None
41
+ org: PlexOrg | None = None
42
+ stripesize: int | None = None
43
+ volume: bytes | None = None
44
+ state: PlexState | None = None
45
+
46
+
47
+ @dataclass
48
+ class SD:
49
+ """The representation of a Vinum SD.
50
+
51
+ A Vinum SD contains information about the actual physical disk and points
52
+ to the device of this disk.
53
+ """
54
+
55
+ timestamp: datetime
56
+ drive: bytes
57
+ name: bytes | None = None
58
+ # length is the size in bytes of the data section on disk, so without any
59
+ # vinum headers etc.
60
+ length: int | None = None
61
+ # driveoffset is the start of the data section on disk in bytes.
62
+ driveoffset: int | None = None
63
+ plex: bytes | None = None
64
+ # plexoffset is the offset of the data section of this disk within the plex in
65
+ # bytes, e.g.: the first disk always starts at offset 0, if the size of its
66
+ # data section (SD.length) is 1024b then the plexoffset for the second disk
67
+ # will be 1024.
68
+ plexoffset: int | None = None
69
+ state: SDState | None = None
70
+
71
+
72
+ class ParseError(Exception):
73
+ pass
74
+
75
+
76
+ class BytesDefaultEnum(bytes, Enum):
77
+ @classmethod
78
+ def _missing_(cls, value):
79
+ return cls._default
80
+
81
+
82
+ class VolumeState(BytesDefaultEnum):
83
+ DOWN = auto()
84
+ UP = b"up"
85
+
86
+ _default = DOWN
87
+
88
+
89
+ class PlexState(BytesDefaultEnum):
90
+ DOWN = auto()
91
+ UP = b"up"
92
+ INITIALIZING = b"initializing"
93
+ DEGRADED = b"degraded"
94
+ GROWABLE = b"growable"
95
+
96
+ _default = DOWN
97
+
98
+
99
+ class PlexOrg(BytesDefaultEnum):
100
+ DISORG = auto()
101
+ CONCAT = b"concat"
102
+ STRIPED = b"striped"
103
+ RAID5 = b"raid5"
104
+
105
+ _default = DISORG
106
+
107
+
108
+ class SDState(BytesDefaultEnum):
109
+ DOWN = auto()
110
+ UP = b"up"
111
+ INITIALIZING = b"initializing"
112
+ DEGRADED = b"degraded"
113
+ GROWABLE = b"growable"
114
+
115
+ _default = DOWN
116
+
117
+
118
+ def _parse_size(size: bytes) -> int:
119
+ # Only the first byte after the numerals (and optional minus sign) should
120
+ # be considered.
121
+ postfix = size.lstrip(b"-0123456789")
122
+ if postfix:
123
+ numeral = size[: -len(postfix)]
124
+ else:
125
+ numeral = size
126
+ unit = postfix[:1]
127
+
128
+ try:
129
+ size = int(numeral)
130
+ except ValueError:
131
+ # If there are no numerals (numeral is empty or the minus sign), the
132
+ # size should be parsed as 0.
133
+ size = 0
134
+ else:
135
+ if unit:
136
+ # Invalid unites should be ignored and size is returned as is.
137
+ if unit in (b"b", b"B", b"s", b"S"):
138
+ size = size * 512 # Yes also for b/B
139
+ elif unit in (b"k", b"K"):
140
+ size = size * 1024
141
+ elif unit in (b"m", b"M"):
142
+ size = size * 1024 * 1024
143
+ elif unit in (b"g", b"G"):
144
+ size = size * 1024 * 1024 * 1024
145
+
146
+ return size
147
+
148
+
149
+ def _parse_volume_config(config_time: datetime, tokens: list[bytes]) -> Volume | None:
150
+ volume = None
151
+ name = None
152
+ state = None
153
+
154
+ tokens = iter(tokens)
155
+ token = next(tokens, None)
156
+ try:
157
+ while token is not None:
158
+ if token == b"state":
159
+ state = VolumeState(next(tokens))
160
+ else:
161
+ name = token
162
+ token = next(tokens, None)
163
+ except StopIteration:
164
+ log.debug("No value for token %r, ignoring volume config", token)
165
+ else:
166
+ if name is None:
167
+ log.debug("No name found for volume, ignoring volume config")
168
+ else:
169
+ volume = Volume(
170
+ timestamp=config_time,
171
+ name=name,
172
+ state=state,
173
+ )
174
+
175
+ return volume
176
+
177
+
178
+ def _parse_plex_config(config_time: datetime, tokens: list[bytes]) -> Plex | None:
179
+ plex = None
180
+ name = None
181
+ org = None
182
+ stripesize = None
183
+ volume = None
184
+ state = None
185
+
186
+ tokens = iter(tokens)
187
+ token = next(tokens, None)
188
+ try:
189
+ while token is not None:
190
+ if token == b"name":
191
+ name = next(tokens)
192
+ elif token == b"org":
193
+ org = PlexOrg(next(tokens))
194
+ if org == PlexOrg.RAID5 or org == PlexOrg.STRIPED:
195
+ stripesize = _parse_size(next(tokens))
196
+ # the kernel parser only checks on == 0, but < 0 also seems unreasonable
197
+ if stripesize <= 0:
198
+ raise ParseError(f"Invalid stripesize: {stripesize}")
199
+ elif token == b"vol" or token == b"volume":
200
+ volume = next(tokens)
201
+ elif token == b"state":
202
+ state = PlexState(next(tokens))
203
+ else:
204
+ raise ParseError(f"Unknown token {token}")
205
+
206
+ token = next(tokens, None)
207
+
208
+ except (StopIteration, ParseError) as err:
209
+ if isinstance(err, StopIteration):
210
+ log.debug("No value for token %r, ignoring plex config", token)
211
+ else:
212
+ log.debug("%s, ignoring plex config", err)
213
+
214
+ else:
215
+ plex = Plex(
216
+ timestamp=config_time,
217
+ name=name,
218
+ org=org,
219
+ stripesize=stripesize,
220
+ volume=volume,
221
+ state=state,
222
+ )
223
+
224
+ return plex
225
+
226
+
227
+ def _parse_sd_config(config_time: datetime, tokens: list[bytes]) -> SD | None:
228
+ sd = None
229
+ name = None
230
+ drive = None
231
+ length = None
232
+ driveoffset = None
233
+ plex = None
234
+ plexoffset = None
235
+ state = None
236
+
237
+ tokens = iter(tokens)
238
+ token = next(tokens, None)
239
+ try:
240
+ while token is not None:
241
+ if token == b"name":
242
+ name = next(tokens)
243
+ elif token == b"drive":
244
+ drive = next(tokens)
245
+ elif token == b"len" or token == b"length":
246
+ length = _parse_size(next(tokens))
247
+ if length < 0:
248
+ length = -1
249
+ elif token == b"driveoffset":
250
+ driveoffset = _parse_size(next(tokens))
251
+ if driveoffset != 0 and driveoffset < c_vinum.GV_DATA_START:
252
+ raise ParseError(f"Invalid driveoffset: {driveoffset}")
253
+ elif token == b"plex":
254
+ plex = next(tokens)
255
+ elif token == b"plexoffset":
256
+ plexoffset = _parse_size(next(tokens))
257
+ if plexoffset < 0:
258
+ raise ParseError(f"Invalid plexoffset: {plexoffset}")
259
+ elif token == b"state":
260
+ state = SDState(next(tokens))
261
+ else:
262
+ raise ParseError(f"Unknown token {token}")
263
+
264
+ token = next(tokens, None)
265
+
266
+ except (StopIteration, ParseError) as err:
267
+ if isinstance(err, StopIteration):
268
+ log.debug("No value for token %r, ignoring sd config", token)
269
+ else:
270
+ log.debug("%s, ignoring sd config", err)
271
+
272
+ else:
273
+ if drive is None:
274
+ log.debug("No drive found for sd, ignoring sd config")
275
+ else:
276
+ sd = SD(
277
+ timestamp=config_time,
278
+ name=name,
279
+ drive=drive,
280
+ length=length,
281
+ driveoffset=driveoffset,
282
+ plex=plex,
283
+ plexoffset=plexoffset,
284
+ state=state,
285
+ )
286
+
287
+ return sd
288
+
289
+
290
+ def get_char(line: bytes, idx: int) -> bytes:
291
+ """Return a single byte bytestring at index ``idx`` in ``line``.
292
+
293
+ If the index is outside of the bounaries of ``line``, an empty bytestring
294
+ will be returned.
295
+ """
296
+ char = b""
297
+ if idx >= 0 and idx < len(line):
298
+ char = line[idx : idx + 1] # this makes sure we get a single byte bytestring
299
+ return char
300
+
301
+
302
+ class TokenizeError(Exception):
303
+ pass
304
+
305
+
306
+ def tokenize(line: bytes) -> iter[bytes]:
307
+ """Yield individual tokens from a vinum config line.
308
+
309
+ This token parser is constructed to be equivalent to the token parser used in the
310
+ FreeBSD kernel code. There are a few caveats though:
311
+
312
+ - it expects lines to be pre-splitted on newline and null-byte characters
313
+ - it does not attempt to parse quoted tokens, as the code in the kernel parser is
314
+ buggy and will always lead to an error condition (it will mimick the error condition
315
+ though).
316
+ """
317
+ whitespace = {b" ", b"\t"}
318
+ quotes = {b'"', b"'"}
319
+ comment = {b"#"}
320
+ eol = {b""}
321
+ end_of_list = eol.union(comment)
322
+ end_of_token = whitespace.union(eol)
323
+
324
+ token = b""
325
+ idx = 0
326
+ while True:
327
+ char = get_char(line, idx)
328
+
329
+ while char in whitespace:
330
+ # Remove leading whitespace up to the next token or end_of_list condition
331
+ idx += 1
332
+ char = get_char(line, idx)
333
+
334
+ if char in end_of_list:
335
+ # We are at the end of the token list (a comment or end of line).
336
+ break
337
+
338
+ if char in quotes:
339
+ # Encountering a quoted token will always lead to an error
340
+ # condition in the (Free)BSD vinum kernel code. This is a bug in
341
+ # that code, which we mimick here.
342
+ raise TokenizeError(f"Found quoted token at index {idx}")
343
+
344
+ while char not in end_of_token:
345
+ # Add characters to the token until we encounter a stop condition.
346
+ # Note that comment and quote characters are allowed in a token as
347
+ # long as they are not preceded by whitespace.
348
+ token += char
349
+ idx += 1
350
+ char = get_char(line, idx)
351
+
352
+ if token:
353
+ yield token
354
+ token = b""
355
+
356
+ idx += 1
357
+
358
+
359
+ class VinumConfigs(TypedDict):
360
+ volumes: list[Volume]
361
+ plexes: list[Plex]
362
+ sds: list[SD]
363
+
364
+
365
+ RE_CONFIG_EOL = re.compile(b"[\x00\n]")
366
+
367
+
368
+ TOKEN_CONFIG_MAP = {
369
+ b"volume": "volumes",
370
+ b"plex": "plexes",
371
+ b"sd": "sds",
372
+ }
373
+
374
+
375
+ def parse_vinum_config(config_time: datetime, config: bytes) -> VinumConfigs:
376
+ """Parse the on-disk vinum configuration.
377
+
378
+ Parsing forgiveness and strictness is implemented in the same way as in the vinum kernel code:
379
+
380
+ Lines with an unknown configuration "type" (not b"volume", b"plex" or b"sd"), are ignored.
381
+
382
+ Lines that fail to parse due to:
383
+ - no name present
384
+ - no value present for a token
385
+ - unknown token name
386
+ - a tokenization error
387
+
388
+ will fail that line and the subsequent lines (rest of the config) to not being parsed.
389
+ """
390
+ config_data: VinumConfigs = {
391
+ "volumes": [],
392
+ "plexes": [],
393
+ "sds": [],
394
+ }
395
+
396
+ for line in RE_CONFIG_EOL.split(config):
397
+ try:
398
+ tokens = tokenize(line)
399
+ token = next(tokens, None)
400
+ if token is None:
401
+ # We encountered a line without tokens (empty, just whitespace or # comments)
402
+ continue
403
+ if token == b"volume":
404
+ parsed_config = _parse_volume_config(config_time, tokens)
405
+ elif token == b"plex":
406
+ parsed_config = _parse_plex_config(config_time, tokens)
407
+ elif token == b"sd":
408
+ parsed_config = _parse_sd_config(config_time, tokens)
409
+ else:
410
+ parsed_config = None
411
+ log.debug("Unknown config type in line: %r, ignoring config line", line)
412
+
413
+ if parsed_config:
414
+ config_type = TOKEN_CONFIG_MAP[token]
415
+ config_data[config_type].append(parsed_config)
416
+ else:
417
+ log.debug("Invalid config line %r", line)
418
+ log.debug("Ignoring this line and the rest of the config data")
419
+ break
420
+ except TokenizeError as err:
421
+ log.debug("Invalid config line %r: %s", line, err)
422
+ log.debug("Ignoring this line and the rest of the config data")
423
+ break
424
+
425
+ return config_data