remap-badblocks 0.8__py3-none-any.whl → 0.8.1__py3-none-any.whl
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.
- remap_badblocks/__init__.py +1 -1
- remap_badblocks-0.8.1.dist-info/METADATA +107 -0
- remap_badblocks-0.8.1.dist-info/RECORD +36 -0
- __init__.py +0 -0
- cli/__init__.py +0 -0
- cli/__main__.py +0 -239
- cli/commands/__init__.py +0 -8
- cli/commands/add.py +0 -91
- cli/commands/apply.py +0 -81
- cli/commands/get.py +0 -28
- cli/commands/remove.py +0 -45
- cli/commands/update.py +0 -208
- cli/commands/version.py +0 -15
- remap_badblocks-0.8.dist-info/METADATA +0 -130
- remap_badblocks-0.8.dist-info/RECORD +0 -66
- src/badblocks/_compute_good_ranges.py +0 -43
- src/badblocks/_find_badblocks.py +0 -76
- src/badblocks/_mapping_generation.py +0 -12
- src/badblocks/_remap_badblocks.py +0 -114
- src/badblocks/badblocks.py +0 -40
- src/devices/__init__.py +0 -0
- src/devices/device_config.py +0 -62
- src/devices/devices_config.py +0 -300
- src/devices/exceptions.py +0 -22
- src/devices_config_constants.py +0 -3
- src/mapping.py +0 -109
- src/remappers/_check_applied_devices.py +0 -10
- src/remappers/_generate_dm_table.py +0 -27
- src/test_utils.py +0 -18
- src/utils/__init__.py +0 -0
- src/utils/_get_device_info.py +0 -43
- src/utils/_iterable_bytes_converter.py +0 -19
- src/utils/_parse_inputs.py +0 -84
- src/utils/_run_command.py +0 -76
- src/utils/_sort_devices.py +0 -26
- {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/WHEEL +0 -0
- {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/entry_points.txt +0 -0
- {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/top_level.txt +0 -0
src/badblocks/badblocks.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from typing import Iterable, Iterator
|
|
3
|
-
|
|
4
|
-
from remap_badblocks.src.utils._iterable_bytes_converter import (
|
|
5
|
-
iterable_from_bytes, iterable_to_bytes)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass
|
|
9
|
-
class Badblocks(Iterable[int]):
|
|
10
|
-
badblocks: set[int]
|
|
11
|
-
|
|
12
|
-
INT_LENGTH: int = 8
|
|
13
|
-
|
|
14
|
-
def __init__(self, badblocks: Iterable[int]):
|
|
15
|
-
self.badblocks = set(badblocks)
|
|
16
|
-
|
|
17
|
-
def __bytes__(self) -> bytes:
|
|
18
|
-
"""
|
|
19
|
-
Convert the Badblocks to bytes for storage.
|
|
20
|
-
"""
|
|
21
|
-
return iterable_to_bytes(self.badblocks, length=self.INT_LENGTH)
|
|
22
|
-
|
|
23
|
-
@classmethod
|
|
24
|
-
def from_bytes(cls, data: bytes) -> "Badblocks":
|
|
25
|
-
"""
|
|
26
|
-
Create a Badblocks instance from bytes.
|
|
27
|
-
"""
|
|
28
|
-
return cls(badblocks=set(iterable_from_bytes(data, length=cls.INT_LENGTH)))
|
|
29
|
-
|
|
30
|
-
def __len__(self) -> int:
|
|
31
|
-
"""
|
|
32
|
-
Get the number of bad blocks.
|
|
33
|
-
"""
|
|
34
|
-
return len(self.badblocks)
|
|
35
|
-
|
|
36
|
-
def __iter__(self) -> Iterator[int]:
|
|
37
|
-
"""
|
|
38
|
-
Iterate over the bad blocks.
|
|
39
|
-
"""
|
|
40
|
-
return iter(self.badblocks)
|
src/devices/__init__.py
DELETED
|
File without changes
|
src/devices/device_config.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Iterable, Optional, Union
|
|
4
|
-
|
|
5
|
-
from remap_badblocks.src.badblocks.badblocks import Badblocks
|
|
6
|
-
from remap_badblocks.src.mapping import Mapping
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@dataclass
|
|
10
|
-
class DeviceConfig:
|
|
11
|
-
id: int
|
|
12
|
-
path: Path
|
|
13
|
-
name: str
|
|
14
|
-
sector_size: int
|
|
15
|
-
badblocks: Badblocks
|
|
16
|
-
depends_on: set[int]
|
|
17
|
-
logical_range: tuple[int, int]
|
|
18
|
-
apply_at_startup: bool = False
|
|
19
|
-
spare_sectors: Optional[int] = None
|
|
20
|
-
mapping: Optional[Mapping] = None
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
id: int,
|
|
25
|
-
path: Union[Path, str],
|
|
26
|
-
name: str,
|
|
27
|
-
sector_size: int,
|
|
28
|
-
badblocks: Iterable[int],
|
|
29
|
-
logical_range: tuple[int, int],
|
|
30
|
-
apply_at_startup: bool = False,
|
|
31
|
-
depends_on: Iterable[int] = set(),
|
|
32
|
-
spare_sectors: Optional[int] = None,
|
|
33
|
-
mapping: Optional[Mapping] = None,
|
|
34
|
-
):
|
|
35
|
-
if isinstance(path, str):
|
|
36
|
-
path = Path(path)
|
|
37
|
-
self.id = id
|
|
38
|
-
self.path = path
|
|
39
|
-
self.name = name
|
|
40
|
-
self.sector_size = sector_size
|
|
41
|
-
self.badblocks = Badblocks(badblocks)
|
|
42
|
-
self.mapping = mapping if mapping is not None else None
|
|
43
|
-
self.spare_sectors = spare_sectors
|
|
44
|
-
self.depends_on = set(depends_on)
|
|
45
|
-
self.logical_range = logical_range
|
|
46
|
-
self.apply_at_startup = apply_at_startup
|
|
47
|
-
|
|
48
|
-
def __str__(self) -> str:
|
|
49
|
-
return (
|
|
50
|
-
"DeviceConfig("
|
|
51
|
-
f" id={self.id}, path={self.path}, name={self.name},"
|
|
52
|
-
f" sector_size={self.sector_size}, badblocks={len(self.badblocks)}, spare_sectors={self.spare_sectors},"
|
|
53
|
-
f" mapping={'Undefined' if self.mapping is None else 'Defined'},"
|
|
54
|
-
f" depends_on={self.depends_on}, logical_range={self.logical_range}"
|
|
55
|
-
")"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
def get_applied_path(self) -> Path:
|
|
59
|
-
return Path("/dev/mapper/", self.name)
|
|
60
|
-
|
|
61
|
-
def __hash__(self) -> int:
|
|
62
|
-
return self.id
|
src/devices/devices_config.py
DELETED
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
import sqlite3 as sqlite
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import (Any, Collection, Iterable, Literal, Optional, Union,
|
|
7
|
-
overload)
|
|
8
|
-
|
|
9
|
-
from remap_badblocks.src.badblocks.badblocks import Badblocks
|
|
10
|
-
from remap_badblocks.src.mapping import Mapping
|
|
11
|
-
from remap_badblocks.src.utils._iterable_bytes_converter import (
|
|
12
|
-
iterable_from_bytes, iterable_to_bytes)
|
|
13
|
-
|
|
14
|
-
from .device_config import DeviceConfig
|
|
15
|
-
from .exceptions import DeviceNotFoundError
|
|
16
|
-
|
|
17
|
-
DEFAULT_INT_LENGTH = 8
|
|
18
|
-
DEVICE_INFO_COLUMNS_TYPES = OrderedDict(
|
|
19
|
-
{
|
|
20
|
-
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
21
|
-
"path": "TEXT NOT NULL",
|
|
22
|
-
"name": "TEXT NOT NULL",
|
|
23
|
-
"sector_size": "INTEGER NOT NULL",
|
|
24
|
-
"badblocks": "BLOB NOT NULL",
|
|
25
|
-
"spare_sectors": "INTEGER",
|
|
26
|
-
"mapping": "BLOB",
|
|
27
|
-
"depends_on": "BLOB NOT NULL",
|
|
28
|
-
"logical_range_start": "INTEGER NOT NULL",
|
|
29
|
-
"logical_range_end": "INTEGER NOT NULL",
|
|
30
|
-
"apply_at_startup": "BOOLEAN DEFAULT FALSE NOT NULL",
|
|
31
|
-
}
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class DevicesConfig:
|
|
36
|
-
_path: Path
|
|
37
|
-
|
|
38
|
-
def __init__(self, path: Union[Path, str]):
|
|
39
|
-
if isinstance(path, str):
|
|
40
|
-
path = Path(path)
|
|
41
|
-
assert not path.is_dir(), "The provided path must not be a directory."
|
|
42
|
-
self._path = path
|
|
43
|
-
self.__init_table()
|
|
44
|
-
|
|
45
|
-
def __init_table(self) -> None:
|
|
46
|
-
"""
|
|
47
|
-
Initialize the devices table in the database.
|
|
48
|
-
"""
|
|
49
|
-
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
|
50
|
-
with sqlite.connect(self._path) as conn:
|
|
51
|
-
cursor = conn.cursor()
|
|
52
|
-
cursor.execute(
|
|
53
|
-
"""
|
|
54
|
-
CREATE TABLE IF NOT EXISTS devices (
|
|
55
|
-
"""
|
|
56
|
-
+ ",".join(
|
|
57
|
-
(
|
|
58
|
-
col_name + " " + col_type
|
|
59
|
-
for col_name, col_type in DEVICE_INFO_COLUMNS_TYPES.items()
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
+ """
|
|
63
|
-
)
|
|
64
|
-
"""
|
|
65
|
-
)
|
|
66
|
-
conn.commit()
|
|
67
|
-
|
|
68
|
-
def __check_device_in_db(self, conn: sqlite.Connection, id: int) -> bool:
|
|
69
|
-
"""
|
|
70
|
-
Check if a device with the given ID exists in the database.
|
|
71
|
-
"""
|
|
72
|
-
cursor = conn.cursor()
|
|
73
|
-
cursor.execute("SELECT id FROM devices WHERE id = ? LIMIT 1", (id,))
|
|
74
|
-
val = cursor.fetchone()
|
|
75
|
-
return val is not None
|
|
76
|
-
|
|
77
|
-
def _assert_depends_on(
|
|
78
|
-
self, conn: sqlite.Connection, depends_on: Collection[int]
|
|
79
|
-
) -> None:
|
|
80
|
-
missing: set[int] = set()
|
|
81
|
-
for _id in depends_on:
|
|
82
|
-
if not self.__check_device_in_db(conn, id=_id):
|
|
83
|
-
missing.add(_id)
|
|
84
|
-
if missing:
|
|
85
|
-
raise DeviceNotFoundError(device_id=missing)
|
|
86
|
-
|
|
87
|
-
def __parse_device_from_row(
|
|
88
|
-
self,
|
|
89
|
-
id: int,
|
|
90
|
-
path: Union[Path, str],
|
|
91
|
-
name: str,
|
|
92
|
-
sector_size: int,
|
|
93
|
-
badblocks: bytes,
|
|
94
|
-
spare_sectors: Optional[int],
|
|
95
|
-
mapping: Optional[bytes],
|
|
96
|
-
depends_on: bytes,
|
|
97
|
-
logical_range_start: int,
|
|
98
|
-
logical_range_end: int,
|
|
99
|
-
apply_at_startup: bool,
|
|
100
|
-
) -> DeviceConfig:
|
|
101
|
-
"""
|
|
102
|
-
Parse a device configuration from a database row.
|
|
103
|
-
"""
|
|
104
|
-
return DeviceConfig(
|
|
105
|
-
id=id,
|
|
106
|
-
path=Path(path),
|
|
107
|
-
name=name,
|
|
108
|
-
sector_size=sector_size,
|
|
109
|
-
badblocks=Badblocks.from_bytes(badblocks),
|
|
110
|
-
spare_sectors=spare_sectors,
|
|
111
|
-
mapping=Mapping.from_bytes(mapping) if mapping is not None else None,
|
|
112
|
-
depends_on=set(iterable_from_bytes(depends_on, length=DEFAULT_INT_LENGTH)),
|
|
113
|
-
logical_range=(logical_range_start, logical_range_end),
|
|
114
|
-
apply_at_startup=apply_at_startup,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
def get_devices(self) -> Iterable[DeviceConfig]:
|
|
118
|
-
"""
|
|
119
|
-
Get the devices configuration as a DataFrame.
|
|
120
|
-
"""
|
|
121
|
-
with sqlite.connect(self._path) as conn:
|
|
122
|
-
data = conn.execute(
|
|
123
|
-
"""SELECT """
|
|
124
|
-
+ ",".join(DEVICE_INFO_COLUMNS_TYPES.keys())
|
|
125
|
-
+ """ FROM devices"""
|
|
126
|
-
).fetchall()
|
|
127
|
-
|
|
128
|
-
data = [
|
|
129
|
-
self.__parse_device_from_row(
|
|
130
|
-
**dict(zip(DEVICE_INFO_COLUMNS_TYPES.keys(), row))
|
|
131
|
-
)
|
|
132
|
-
for row in data
|
|
133
|
-
]
|
|
134
|
-
return data
|
|
135
|
-
|
|
136
|
-
@overload
|
|
137
|
-
def get_device(self, *, id: int, name: Literal[None] = None) -> DeviceConfig: ...
|
|
138
|
-
@overload
|
|
139
|
-
def get_device(self, *, id: Literal[None] = None, name: str) -> DeviceConfig: ...
|
|
140
|
-
|
|
141
|
-
def get_device(
|
|
142
|
-
self, *, id: Optional[int] = None, name: Optional[str] = None
|
|
143
|
-
) -> DeviceConfig:
|
|
144
|
-
if id is not None and name is not None:
|
|
145
|
-
raise ValueError("Only one of id or name can be provided to get a device.")
|
|
146
|
-
|
|
147
|
-
with sqlite.connect(self._path) as conn:
|
|
148
|
-
cursor = conn.cursor()
|
|
149
|
-
QUERY = (
|
|
150
|
-
"""SELECT """
|
|
151
|
-
+ ",".join(DEVICE_INFO_COLUMNS_TYPES.keys())
|
|
152
|
-
+ """ FROM devices"""
|
|
153
|
-
)
|
|
154
|
-
if id is not None:
|
|
155
|
-
cursor.execute(QUERY + " WHERE id = ?", (id,))
|
|
156
|
-
elif name is not None:
|
|
157
|
-
cursor.execute(QUERY + " WHERE name = ?", (name,))
|
|
158
|
-
else:
|
|
159
|
-
raise ValueError("Either id or name must be provided to get a device.")
|
|
160
|
-
row = cursor.fetchone()
|
|
161
|
-
if row is None:
|
|
162
|
-
raise DeviceNotFoundError(device_id=id, device_name=name)
|
|
163
|
-
|
|
164
|
-
return self.__parse_device_from_row(
|
|
165
|
-
**dict(zip(DEVICE_INFO_COLUMNS_TYPES.keys(), row))
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
def __insert_into_devices_table(self, cursor: sqlite.Cursor, row: dict[str, Any]):
|
|
169
|
-
cols = list(row.keys())
|
|
170
|
-
cursor.execute(
|
|
171
|
-
"""INSERT INTO devices
|
|
172
|
-
("""
|
|
173
|
-
+ ",".join(cols)
|
|
174
|
-
+ """)
|
|
175
|
-
VALUES ("""
|
|
176
|
-
+ ",".join(("?",) * len(cols))
|
|
177
|
-
+ """)""",
|
|
178
|
-
tuple(row[col] for col in cols),
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
def add_device(
|
|
182
|
-
self,
|
|
183
|
-
path: Union[Path, str],
|
|
184
|
-
name: str,
|
|
185
|
-
logical_range: tuple[int, int],
|
|
186
|
-
*,
|
|
187
|
-
sector_size: Optional[int] = None,
|
|
188
|
-
badblocks: Union[set[int], Badblocks] = set(),
|
|
189
|
-
depends_on: Collection[int] = set(),
|
|
190
|
-
) -> None:
|
|
191
|
-
_path: Path = Path(path)
|
|
192
|
-
assert _path.exists(), f"The provided device {_path} does not exist."
|
|
193
|
-
|
|
194
|
-
assert (
|
|
195
|
-
re.fullmatch(r"[0-9A-Za-z#+\-\.:=@_]+", name) is not None
|
|
196
|
-
), "Name does not match requirements"
|
|
197
|
-
|
|
198
|
-
try:
|
|
199
|
-
existing_dev = self.get_device(name=name)
|
|
200
|
-
raise ValueError(f"Name {name} is used by device {existing_dev.id}.")
|
|
201
|
-
except DeviceNotFoundError:
|
|
202
|
-
pass
|
|
203
|
-
|
|
204
|
-
badblocks = Badblocks(badblocks)
|
|
205
|
-
|
|
206
|
-
with sqlite.connect(self._path) as conn:
|
|
207
|
-
self._assert_depends_on(conn, depends_on)
|
|
208
|
-
|
|
209
|
-
cursor = conn.cursor()
|
|
210
|
-
self.__insert_into_devices_table(
|
|
211
|
-
cursor,
|
|
212
|
-
{
|
|
213
|
-
"path": str(_path),
|
|
214
|
-
"name": name,
|
|
215
|
-
"sector_size": sector_size,
|
|
216
|
-
"badblocks": sqlite.Binary(bytes(badblocks)),
|
|
217
|
-
"spare_sectors": None,
|
|
218
|
-
"mapping": None,
|
|
219
|
-
"depends_on": iterable_to_bytes(
|
|
220
|
-
depends_on, length=DEFAULT_INT_LENGTH
|
|
221
|
-
),
|
|
222
|
-
"logical_range_start": logical_range[0],
|
|
223
|
-
"logical_range_end": logical_range[1],
|
|
224
|
-
},
|
|
225
|
-
)
|
|
226
|
-
conn.commit()
|
|
227
|
-
|
|
228
|
-
def remove_device(self, id: int) -> None:
|
|
229
|
-
with sqlite.connect(self._path) as conn:
|
|
230
|
-
if not self.__check_device_in_db(conn, id=id):
|
|
231
|
-
raise DeviceNotFoundError(device_id=id)
|
|
232
|
-
|
|
233
|
-
for dev in self.get_devices():
|
|
234
|
-
if id in dev.depends_on:
|
|
235
|
-
raise KeyError(
|
|
236
|
-
f"Device {dev.id} depends on device {id}, can't remove the latter."
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
cursor = conn.cursor()
|
|
240
|
-
cursor.execute("DELETE FROM devices WHERE id = ?", (id,))
|
|
241
|
-
conn.commit()
|
|
242
|
-
|
|
243
|
-
def update_device(
|
|
244
|
-
self,
|
|
245
|
-
id: int,
|
|
246
|
-
*,
|
|
247
|
-
path: Optional[Union[Path, str]] = None,
|
|
248
|
-
name: Optional[str] = None,
|
|
249
|
-
badblocks: Optional[Iterable[int]] = None,
|
|
250
|
-
mapping: Optional[Mapping] = None,
|
|
251
|
-
spare_sectors: Optional[int] = None,
|
|
252
|
-
depends_on: Optional[Collection[int]] = None,
|
|
253
|
-
) -> None:
|
|
254
|
-
with sqlite.connect(self._path) as conn:
|
|
255
|
-
if not self.__check_device_in_db(conn, id=id):
|
|
256
|
-
raise DeviceNotFoundError(device_id=id)
|
|
257
|
-
|
|
258
|
-
cursor = conn.cursor()
|
|
259
|
-
if path is not None:
|
|
260
|
-
cursor.execute(
|
|
261
|
-
"UPDATE devices SET path = ? WHERE id = ?", (str(path), id)
|
|
262
|
-
)
|
|
263
|
-
if name is not None:
|
|
264
|
-
cursor.execute("UPDATE devices SET name = ? WHERE id = ?", (name, id))
|
|
265
|
-
if badblocks is not None:
|
|
266
|
-
badblocks = Badblocks(badblocks)
|
|
267
|
-
cursor.execute(
|
|
268
|
-
"UPDATE devices SET badblocks = ? WHERE id = ?",
|
|
269
|
-
(sqlite.Binary(bytes(badblocks)), id),
|
|
270
|
-
)
|
|
271
|
-
if mapping is not None:
|
|
272
|
-
cursor.execute(
|
|
273
|
-
"UPDATE devices SET mapping = ? WHERE id = ?",
|
|
274
|
-
(sqlite.Binary(bytes(mapping)), id),
|
|
275
|
-
)
|
|
276
|
-
if spare_sectors is not None:
|
|
277
|
-
cursor.execute(
|
|
278
|
-
"UPDATE devices SET spare_sectors = ? WHERE id = ?",
|
|
279
|
-
(spare_sectors, id),
|
|
280
|
-
)
|
|
281
|
-
if depends_on is not None:
|
|
282
|
-
self._assert_depends_on(conn, depends_on)
|
|
283
|
-
cursor.execute(
|
|
284
|
-
"UPDATE devices SET depends_on = ? WHERE id = ?",
|
|
285
|
-
(
|
|
286
|
-
iterable_to_bytes(depends_on, length=DEFAULT_INT_LENGTH),
|
|
287
|
-
id,
|
|
288
|
-
),
|
|
289
|
-
)
|
|
290
|
-
conn.commit()
|
|
291
|
-
|
|
292
|
-
def __len__(self) -> int:
|
|
293
|
-
"""
|
|
294
|
-
Get the number of devices in the database.
|
|
295
|
-
"""
|
|
296
|
-
with sqlite.connect(self._path) as conn:
|
|
297
|
-
cursor = conn.cursor()
|
|
298
|
-
cursor.execute("SELECT COUNT(*) FROM devices")
|
|
299
|
-
count = cursor.fetchone()[0]
|
|
300
|
-
return count
|
src/devices/exceptions.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
from typing import Iterable, Optional, Union
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class DeviceNotFoundError(KeyError):
|
|
5
|
-
def __init__(
|
|
6
|
-
self,
|
|
7
|
-
*,
|
|
8
|
-
device_id: Optional[Union[int, Iterable[int]]] = None,
|
|
9
|
-
device_name: Optional[Union[str, Iterable[str]]] = None,
|
|
10
|
-
) -> None:
|
|
11
|
-
if device_id is not None and device_name is not None:
|
|
12
|
-
raise ValueError("Only one of device_id or device_name can be provided.")
|
|
13
|
-
if device_id is not None:
|
|
14
|
-
message = f"Device(s) with id(s) {device_id} does not exist."
|
|
15
|
-
args = (message,)
|
|
16
|
-
elif device_name is not None:
|
|
17
|
-
message = f"Device(s) with name(s) '{device_name}' does not exist."
|
|
18
|
-
args = (message,)
|
|
19
|
-
else:
|
|
20
|
-
message = "Device(s) not found."
|
|
21
|
-
args = (message,)
|
|
22
|
-
super().__init__(*args)
|
src/devices_config_constants.py
DELETED
src/mapping.py
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import sqlite3
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Collection, Iterator
|
|
4
|
-
|
|
5
|
-
from remap_badblocks.src.utils._iterable_bytes_converter import (
|
|
6
|
-
iterable_from_bytes, iterable_to_bytes)
|
|
7
|
-
|
|
8
|
-
DEFAULT_INT_LENGTH = 8
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class MappingElement:
|
|
13
|
-
start_id_virtual: int
|
|
14
|
-
start_id_real: int
|
|
15
|
-
length: int
|
|
16
|
-
|
|
17
|
-
INT_LENGTH: int = DEFAULT_INT_LENGTH
|
|
18
|
-
BYTES_LENGTH: int = 3 * INT_LENGTH
|
|
19
|
-
|
|
20
|
-
def __bytes__(self) -> bytes:
|
|
21
|
-
"""
|
|
22
|
-
Convert the MappingElement to bytes for storage.
|
|
23
|
-
"""
|
|
24
|
-
return iterable_to_bytes(
|
|
25
|
-
(
|
|
26
|
-
self.start_id_virtual,
|
|
27
|
-
self.start_id_real,
|
|
28
|
-
self.length,
|
|
29
|
-
),
|
|
30
|
-
length=self.INT_LENGTH,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
def __index__(self) -> int:
|
|
34
|
-
"""
|
|
35
|
-
Convert the MappingElement to an integer for storage.
|
|
36
|
-
"""
|
|
37
|
-
return int.from_bytes(self.__bytes__(), "big")
|
|
38
|
-
|
|
39
|
-
@classmethod
|
|
40
|
-
def from_bytes(cls, data: bytes) -> "MappingElement":
|
|
41
|
-
"""
|
|
42
|
-
Create a MappingElement from bytes.
|
|
43
|
-
"""
|
|
44
|
-
if len(data) != cls.BYTES_LENGTH:
|
|
45
|
-
raise ValueError(f"Data must be exactly {cls.BYTES_LENGTH} bytes long.")
|
|
46
|
-
start_id_virtual, start_id_real, length = iterable_from_bytes(
|
|
47
|
-
data, length=cls.INT_LENGTH
|
|
48
|
-
)
|
|
49
|
-
return cls(start_id_virtual, start_id_real, length)
|
|
50
|
-
|
|
51
|
-
def __hash__(self) -> int:
|
|
52
|
-
"""
|
|
53
|
-
Hash the MappingElement for use in sets or dictionaries.
|
|
54
|
-
"""
|
|
55
|
-
return self.__index__()
|
|
56
|
-
|
|
57
|
-
@classmethod
|
|
58
|
-
def from_tuple(cls, _tuple: tuple[int, int, int]) -> "MappingElement":
|
|
59
|
-
"""
|
|
60
|
-
Create a MappingElement from a tuple.
|
|
61
|
-
"""
|
|
62
|
-
if len(_tuple) != 3:
|
|
63
|
-
raise ValueError("Tuple must contain exactly three elements.")
|
|
64
|
-
return cls(
|
|
65
|
-
start_id_virtual=_tuple[0], start_id_real=_tuple[1], length=_tuple[2]
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
def to_tuple(self) -> tuple[int, int, int]:
|
|
69
|
-
return self.start_id_virtual, self.start_id_real, self.length
|
|
70
|
-
|
|
71
|
-
def __iter__(self) -> Iterator[int]:
|
|
72
|
-
return iter(self.to_tuple())
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@dataclass
|
|
76
|
-
class Mapping:
|
|
77
|
-
elements: Collection[MappingElement]
|
|
78
|
-
|
|
79
|
-
def __bytes__(self) -> bytes:
|
|
80
|
-
"""
|
|
81
|
-
Convert the Mapping to bytes for storage.
|
|
82
|
-
"""
|
|
83
|
-
return b"".join(bytes(element) for element in self.elements)
|
|
84
|
-
|
|
85
|
-
@classmethod
|
|
86
|
-
def from_bytes(cls, data: bytes) -> "Mapping":
|
|
87
|
-
"""
|
|
88
|
-
Create a Mapping from bytes.
|
|
89
|
-
"""
|
|
90
|
-
if len(data) % MappingElement.BYTES_LENGTH != 0:
|
|
91
|
-
raise ValueError(
|
|
92
|
-
"Data length is not a multiple of {} bytes.",
|
|
93
|
-
MappingElement.BYTES_LENGTH,
|
|
94
|
-
)
|
|
95
|
-
elements: list[MappingElement] = []
|
|
96
|
-
for i in range(0, len(data), MappingElement.BYTES_LENGTH):
|
|
97
|
-
elements.append(
|
|
98
|
-
MappingElement.from_bytes(data[i : i + MappingElement.BYTES_LENGTH])
|
|
99
|
-
)
|
|
100
|
-
return cls(elements)
|
|
101
|
-
|
|
102
|
-
def to_sql_binary(self) -> sqlite3.Binary:
|
|
103
|
-
"""
|
|
104
|
-
Convert the Mapping to a binary format suitable for SQLite storage.
|
|
105
|
-
"""
|
|
106
|
-
return sqlite3.Binary(bytes(self))
|
|
107
|
-
|
|
108
|
-
def __iter__(self) -> Iterator[MappingElement]:
|
|
109
|
-
return iter(self.elements)
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from typing import Iterable
|
|
2
|
-
|
|
3
|
-
from remap_badblocks.src.devices.device_config import DeviceConfig
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def filter_applied_devices(devices: Iterable[DeviceConfig]):
|
|
7
|
-
def check_applied(device: DeviceConfig) -> bool:
|
|
8
|
-
return device.get_applied_path().is_block_device()
|
|
9
|
-
|
|
10
|
-
return filter(check_applied, devices)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Generator, Iterable, TypeVar, Union
|
|
3
|
-
|
|
4
|
-
from remap_badblocks.src.devices.devices_config import Mapping
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def generate_dm_table(
|
|
8
|
-
device: Union[Path, str],
|
|
9
|
-
mapping: Union[Iterable[tuple[int, int, int]], Mapping],
|
|
10
|
-
block_size: int,
|
|
11
|
-
) -> Generator[str, None, None]:
|
|
12
|
-
"""Generate a device-mapper linear mapping table from good ranges."""
|
|
13
|
-
block_size_multiplier = block_size / 512
|
|
14
|
-
assert block_size_multiplier.is_integer(), "Block size must be a multiple of 512"
|
|
15
|
-
block_size_multiplier = int(block_size_multiplier)
|
|
16
|
-
|
|
17
|
-
T = TypeVar("T")
|
|
18
|
-
|
|
19
|
-
def get_first(iterable: Iterable[T]) -> T:
|
|
20
|
-
return next(iter(iterable))
|
|
21
|
-
|
|
22
|
-
for start_virtual, start_real, length in sorted(mapping, key=get_first):
|
|
23
|
-
start_virtual *= block_size_multiplier
|
|
24
|
-
start_real *= block_size_multiplier
|
|
25
|
-
length *= block_size_multiplier
|
|
26
|
-
|
|
27
|
-
yield f"{start_virtual} {length} linear {device} {start_real}"
|
src/test_utils.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from random import shuffle
|
|
2
|
-
from typing import Generator, Iterable, TypeVar
|
|
3
|
-
|
|
4
|
-
T = TypeVar("T")
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def iter_to_shuffled_generator(_iter: Iterable[T]) -> Generator[T, None, None]:
|
|
8
|
-
_iter_list = list(_iter)
|
|
9
|
-
shuffle(_iter_list)
|
|
10
|
-
for v in _iter_list:
|
|
11
|
-
yield v
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def count_sectors_in_ranges(ranges: Iterable[tuple[int, int]]):
|
|
15
|
-
n_sectors = 0
|
|
16
|
-
for start, end in ranges:
|
|
17
|
-
n_sectors += end - start
|
|
18
|
-
return n_sectors
|
src/utils/__init__.py
DELETED
|
File without changes
|
src/utils/_get_device_info.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def resolve_device_name(device: Path) -> str:
|
|
7
|
-
resolved = device.resolve()
|
|
8
|
-
if not resolved.is_block_device():
|
|
9
|
-
raise ValueError(f"{device} is not a valid block device.")
|
|
10
|
-
_resolved = str(resolved)
|
|
11
|
-
m = re.match(r"^/dev/([a-zA-Z0-9\-]+)$", _resolved)
|
|
12
|
-
if not m:
|
|
13
|
-
raise RuntimeError(f"Could not parse '{_resolved}'.")
|
|
14
|
-
return m.group(1)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_disk_block_size(device_name: str) -> int:
|
|
18
|
-
"""Get the block size of the disk."""
|
|
19
|
-
path = os.path.join("/sys/block/", device_name, "queue/physical_block_size")
|
|
20
|
-
try:
|
|
21
|
-
with open(path, "r") as f:
|
|
22
|
-
block_size = f.read()
|
|
23
|
-
return int(block_size.strip())
|
|
24
|
-
except Exception as e:
|
|
25
|
-
raise RuntimeError(f"Could not read block size for {device_name}: {e}") from e
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def get_disk_number_of_blocks(device_name: str) -> int:
|
|
29
|
-
"""Get the number of blocks on the disk."""
|
|
30
|
-
block_size = get_disk_block_size(device_name)
|
|
31
|
-
path = os.path.join("/sys/block/", device_name, "size")
|
|
32
|
-
try:
|
|
33
|
-
with open(path, "r") as f:
|
|
34
|
-
txt = f.read()
|
|
35
|
-
size_in_blocks = int(txt.strip()) * 512 / block_size
|
|
36
|
-
assert (
|
|
37
|
-
size_in_blocks.is_integer()
|
|
38
|
-
), f"Size in blocks is not an integer: {size_in_blocks}"
|
|
39
|
-
return int(size_in_blocks)
|
|
40
|
-
except Exception as e:
|
|
41
|
-
raise RuntimeError(
|
|
42
|
-
f"Could not read size in physical blocks for {device_name}: {e}"
|
|
43
|
-
) from e
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from typing import Iterable
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def iterable_to_bytes(iterable: Iterable[int], length: int = 4) -> bytes:
|
|
5
|
-
"""
|
|
6
|
-
Convert an iterable of integers to bytes.
|
|
7
|
-
"""
|
|
8
|
-
return b"".join(i.to_bytes(length, "big") for i in iterable)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def iterable_from_bytes(data: bytes, length: int = 4) -> Iterable[int]:
|
|
12
|
-
"""
|
|
13
|
-
Convert bytes to an iterable of integers.
|
|
14
|
-
"""
|
|
15
|
-
if len(data) % length != 0:
|
|
16
|
-
raise ValueError(f"Data length must be a multiple of {length} bytes.")
|
|
17
|
-
return (
|
|
18
|
-
int.from_bytes(data[i : i + length], "big") for i in range(0, len(data), length)
|
|
19
|
-
)
|