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.
Files changed (39) hide show
  1. remap_badblocks/__init__.py +1 -1
  2. remap_badblocks-0.8.1.dist-info/METADATA +107 -0
  3. remap_badblocks-0.8.1.dist-info/RECORD +36 -0
  4. __init__.py +0 -0
  5. cli/__init__.py +0 -0
  6. cli/__main__.py +0 -239
  7. cli/commands/__init__.py +0 -8
  8. cli/commands/add.py +0 -91
  9. cli/commands/apply.py +0 -81
  10. cli/commands/get.py +0 -28
  11. cli/commands/remove.py +0 -45
  12. cli/commands/update.py +0 -208
  13. cli/commands/version.py +0 -15
  14. remap_badblocks-0.8.dist-info/METADATA +0 -130
  15. remap_badblocks-0.8.dist-info/RECORD +0 -66
  16. src/badblocks/_compute_good_ranges.py +0 -43
  17. src/badblocks/_find_badblocks.py +0 -76
  18. src/badblocks/_mapping_generation.py +0 -12
  19. src/badblocks/_remap_badblocks.py +0 -114
  20. src/badblocks/badblocks.py +0 -40
  21. src/devices/__init__.py +0 -0
  22. src/devices/device_config.py +0 -62
  23. src/devices/devices_config.py +0 -300
  24. src/devices/exceptions.py +0 -22
  25. src/devices_config_constants.py +0 -3
  26. src/mapping.py +0 -109
  27. src/remappers/_check_applied_devices.py +0 -10
  28. src/remappers/_generate_dm_table.py +0 -27
  29. src/test_utils.py +0 -18
  30. src/utils/__init__.py +0 -0
  31. src/utils/_get_device_info.py +0 -43
  32. src/utils/_iterable_bytes_converter.py +0 -19
  33. src/utils/_parse_inputs.py +0 -84
  34. src/utils/_run_command.py +0 -76
  35. src/utils/_sort_devices.py +0 -26
  36. {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/WHEEL +0 -0
  37. {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/entry_points.txt +0 -0
  38. {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/licenses/LICENSE +0 -0
  39. {remap_badblocks-0.8.dist-info → remap_badblocks-0.8.1.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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
@@ -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)
@@ -1,3 +0,0 @@
1
- from pathlib import Path
2
-
3
- DEFAULT_DEVICES_CONFIG_PATH = Path("/etc/remap_badblocks/devices_config.db")
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
@@ -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
- )