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.
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/.gitattributes +1 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/PKG-INFO +1 -1
- dissect_volume-3.13.dev1/dissect/volume/vinum/c_vinum.py +83 -0
- dissect_volume-3.13.dev1/dissect/volume/vinum/config.py +425 -0
- dissect_volume-3.13.dev1/dissect/volume/vinum/vinum.py +349 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/PKG-INFO +1 -1
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/SOURCES.txt +19 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/conftest.py +54 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diska.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-concat_diskb.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diska.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-mirror_diskb.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diska.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskb.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskc.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-raid5_diskd.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diska.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-striped_diskb.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diska.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskb.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskc.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/data/vinum/vinum-stripedmirror_diskd.bin.gz +0 -0
- dissect_volume-3.13.dev1/tests/test_vinum.py +238 -0
- dissect_volume-3.13.dev1/tests/test_vinum_config.py +460 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/COPYRIGHT +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/LICENSE +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/MANIFEST.in +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/README.md +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/c_ddf.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ddf/ddf.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/disk.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/partition.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/apm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/bsd.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/gpt.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/disk/schemes/mbr.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/btree.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/c_dm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/dm/thin.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/exceptions.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/ldm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/c_lvm2.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/lvm2.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/metadata.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/lvm/physical.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/c_md.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/md/md.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/raid.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/raid/stream.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect/volume/vss.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/dependency_links.txt +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/requires.txt +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/dissect.volume.egg-info/top_level.txt +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/pyproject.toml +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/setup.cfg +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/__init__.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/apm.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/bsd.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/bsd64.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid0-raid1-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid1-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid10-4.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid4-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid5-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/ddf/ddf-raid6-4.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-data.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-data.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-empty-metadata.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/dm/dm-thin-metadata.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_4k.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_esxi.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_hybrid.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/gpt_no_name_xff.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-mirror-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm-thin.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/lvm/lvm.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/mbr.bin +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-90-raid1-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-linear-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid0-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid1-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid10-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid4-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid5-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-1.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-2.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-3.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/data/md/md-raid6-4.bin.gz +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/Makefile +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/conf.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/docs/index.rst +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_apm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_bsd.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_ddf.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_dm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_gpt.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_lvm.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_mbr.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tests/test_md.py +0 -0
- {dissect_volume-3.12.dev3 → dissect_volume-3.13.dev1}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.volume
|
|
3
|
-
Version: 3.
|
|
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
|