remap-badblocks 0.8__py3-none-any.whl → 0.9__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 (42) hide show
  1. remap_badblocks/__init__.py +1 -1
  2. remap_badblocks/cli/commands/update.py +8 -6
  3. remap_badblocks/src/badblocks/_remap_badblocks.py +23 -23
  4. remap_badblocks/src/mapping.py +5 -1
  5. remap_badblocks-0.9.dist-info/METADATA +107 -0
  6. remap_badblocks-0.9.dist-info/RECORD +36 -0
  7. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/WHEEL +1 -1
  8. __init__.py +0 -0
  9. cli/__init__.py +0 -0
  10. cli/__main__.py +0 -239
  11. cli/commands/__init__.py +0 -8
  12. cli/commands/add.py +0 -91
  13. cli/commands/apply.py +0 -81
  14. cli/commands/get.py +0 -28
  15. cli/commands/remove.py +0 -45
  16. cli/commands/update.py +0 -208
  17. cli/commands/version.py +0 -15
  18. remap_badblocks-0.8.dist-info/METADATA +0 -130
  19. remap_badblocks-0.8.dist-info/RECORD +0 -66
  20. src/badblocks/_compute_good_ranges.py +0 -43
  21. src/badblocks/_find_badblocks.py +0 -76
  22. src/badblocks/_mapping_generation.py +0 -12
  23. src/badblocks/_remap_badblocks.py +0 -114
  24. src/badblocks/badblocks.py +0 -40
  25. src/devices/__init__.py +0 -0
  26. src/devices/device_config.py +0 -62
  27. src/devices/devices_config.py +0 -300
  28. src/devices/exceptions.py +0 -22
  29. src/devices_config_constants.py +0 -3
  30. src/mapping.py +0 -109
  31. src/remappers/_check_applied_devices.py +0 -10
  32. src/remappers/_generate_dm_table.py +0 -27
  33. src/test_utils.py +0 -18
  34. src/utils/__init__.py +0 -0
  35. src/utils/_get_device_info.py +0 -43
  36. src/utils/_iterable_bytes_converter.py +0 -19
  37. src/utils/_parse_inputs.py +0 -84
  38. src/utils/_run_command.py +0 -76
  39. src/utils/_sort_devices.py +0 -26
  40. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/entry_points.txt +0 -0
  41. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/licenses/LICENSE +0 -0
  42. {remap_badblocks-0.8.dist-info → remap_badblocks-0.9.dist-info}/top_level.txt +0 -0
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
- )
@@ -1,84 +0,0 @@
1
- import re
2
- from typing import Optional
3
-
4
-
5
- def parse_string_with_unit_to_bytes(txt: str, unit_multiplier: int) -> int:
6
- return round(float(txt) * unit_multiplier)
7
-
8
-
9
- def parse_bytes_to_sectors(_input: int, sector_size: int) -> int:
10
- if _input % sector_size != 0:
11
- raise ValueError(
12
- f"{_input}B is not a multiple of the sector size {sector_size}B."
13
- )
14
- return int(_input / sector_size)
15
-
16
-
17
- def parse_memory_number_to_bytes(txt: str, sector_size: int) -> int:
18
- """
19
- Parses a memory value (a sector number or a space size) and outputs a sector number.
20
- """
21
- txt = txt.strip()
22
-
23
- try:
24
- return parse_string_with_unit_to_bytes(txt, sector_size)
25
- except ValueError:
26
- pass
27
- try:
28
- if txt.endswith("MB"):
29
- return parse_string_with_unit_to_bytes(txt[:-2], 1024**2)
30
- elif txt.endswith("GB"):
31
- return parse_string_with_unit_to_bytes(txt[:-2], 1024**3)
32
- elif txt.endswith("KB"):
33
- return parse_string_with_unit_to_bytes(txt[:-2], 1024)
34
- elif txt.endswith("B"):
35
- return parse_string_with_unit_to_bytes(txt[:-1], 1)
36
- else:
37
- raise ValueError(f"Invalid format: {txt}")
38
- except ValueError as e:
39
- raise ValueError(f"Failed to parse memory space from '{txt}': {e}") from e
40
-
41
-
42
- def parse_memory_range_to_bytes(
43
- txt: str, sector_size: int
44
- ) -> tuple[Optional[int], Optional[int]]:
45
- """
46
- Parse a memory space range from a string and check the format is valid. Returns a sector range.
47
- I.e. checks that the format is 'start-end', where each can be omitted, and start <= end.
48
- """
49
- txt = txt.strip()
50
-
51
- m = re.match(
52
- r"^(?P<start>\d+(\.\d+)?(?:[KMGT]?B)?)?-(?P<end>\d+(\.\d+)?(?:[KMGT]?B)?)?$",
53
- txt,
54
- )
55
- if m is None:
56
- raise ValueError(
57
- f"Invalid format: {txt}. Expected format: 'start-end', where each can be omitted."
58
- )
59
- groups: dict[str, str | None] = {key: m.group(key) for key in ("start", "end")}
60
- values: dict[str, int] = {
61
- key: parse_memory_number_to_bytes(value, sector_size)
62
- for key, value in groups.items()
63
- if value is not None
64
- }
65
-
66
- start, end = values.get("start"), values.get("end")
67
-
68
- if start is not None and end is not None and (end < start):
69
- raise ValueError(f"End {end} must be greater than or equal to start {start}.")
70
-
71
- return start, end
72
-
73
-
74
- def parse_memory_range_to_sectors(
75
- txt: str, sector_size: int
76
- ) -> tuple[Optional[int], Optional[int]]:
77
- start, end = parse_memory_range_to_bytes(txt, sector_size)
78
-
79
- if start is not None:
80
- start = parse_bytes_to_sectors(start, sector_size)
81
- if end is not None:
82
- end = parse_bytes_to_sectors(end, sector_size)
83
-
84
- return start, end
src/utils/_run_command.py DELETED
@@ -1,76 +0,0 @@
1
- import subprocess
2
- import sys
3
- import threading
4
- from pathlib import Path
5
- from typing import IO, Iterable, Optional, Protocol, Union
6
-
7
-
8
- class Stringifyiable(Protocol):
9
- """Protocol for objects that can be converted to a string."""
10
-
11
- def __str__(self) -> str:
12
- """Return the string representation of the object."""
13
- ...
14
-
15
-
16
- def pipe_stderr_to_stream(pipe: IO[str], stream: IO[str]) -> None:
17
- for line in pipe:
18
- print(f"[stderr] {line.strip()}", file=stream)
19
-
20
-
21
- def run_command_realtime(cmd: list[str], stdin: Optional[str] = None) -> Iterable[str]:
22
- """Run a command and yield its output line by line in real-time."""
23
- with subprocess.Popen(
24
- cmd,
25
- stdout=subprocess.PIPE,
26
- stderr=subprocess.PIPE,
27
- stdin=subprocess.PIPE,
28
- text=True,
29
- ) as process:
30
- if process.stdout is None:
31
- raise RuntimeError("Failed to capture stdout from the command.")
32
-
33
- if stdin is not None:
34
- if process.stdin is None:
35
- raise RuntimeError("Failed to pass stdin to command.")
36
- else:
37
- if not stdin.endswith("\n"):
38
- stdin += "\n"
39
- process.stdin.write(stdin)
40
- process.stdin.close()
41
-
42
- # Start background stderr reader
43
- stderr_thread: Optional[threading.Thread] = None
44
- if process.stderr is not None:
45
- stderr_thread = threading.Thread(
46
- target=pipe_stderr_to_stream, args=(process.stderr, sys.stderr)
47
- )
48
- stderr_thread.start()
49
-
50
- for line in process.stdout:
51
- yield line.strip()
52
-
53
- process.wait()
54
- if stderr_thread is not None:
55
- stderr_thread.join()
56
-
57
- if process.returncode != 0:
58
- # Capture stderr if needed
59
- raise RuntimeError(f"Command failed with code {process.returncode}")
60
-
61
-
62
- def pipe_lines_to_file(
63
- lines: Iterable[Stringifyiable], output_file: Union[str, Path, IO[str]]
64
- ) -> None:
65
- """Save the lines to the specified output file in real-time."""
66
- f: Optional[IO[str]] = None
67
- try:
68
- if not isinstance(output_file, IO):
69
- f = open(output_file, "w")
70
- output_file = f
71
- for line in lines:
72
- output_file.write(str(line) + "\n")
73
- output_file.flush() # Ensure the content is written to the file immediately
74
- finally:
75
- if f is not None:
76
- f.close()
@@ -1,26 +0,0 @@
1
- from typing import Collection, Iterable
2
-
3
- from remap_badblocks.src.devices.devices_config import DeviceConfig
4
-
5
-
6
- def sort_devices_by_dependencies(
7
- devices: Collection[DeviceConfig],
8
- already_applied_devices: Iterable[DeviceConfig] = set(),
9
- ) -> Iterable[DeviceConfig]:
10
- devices = list(devices)
11
- sorted_devices: list[DeviceConfig] = []
12
- already_sorted_ids: set[int] = set(map(lambda x: x.id, already_applied_devices))
13
-
14
- while devices:
15
- for device in devices:
16
- if all(dep in already_sorted_ids for dep in device.depends_on):
17
- sorted_devices.append(device)
18
- already_sorted_ids.add(device.id)
19
- devices.remove(device)
20
- break
21
- else:
22
- raise ValueError(
23
- "Circular dependency detected or no device can be applied."
24
- )
25
-
26
- return sorted_devices