remap-badblocks 0.7__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.
- __init__.py +0 -0
- cli/__init__.py +0 -0
- cli/__main__.py +239 -0
- cli/commands/__init__.py +8 -0
- cli/commands/add.py +91 -0
- cli/commands/apply.py +81 -0
- cli/commands/get.py +28 -0
- cli/commands/remove.py +45 -0
- cli/commands/update.py +208 -0
- cli/commands/version.py +15 -0
- remap_badblocks/__init__.py +0 -0
- remap_badblocks/cli/__init__.py +0 -0
- remap_badblocks/cli/__main__.py +239 -0
- remap_badblocks/cli/commands/__init__.py +8 -0
- remap_badblocks/cli/commands/add.py +91 -0
- remap_badblocks/cli/commands/apply.py +81 -0
- remap_badblocks/cli/commands/get.py +28 -0
- remap_badblocks/cli/commands/remove.py +45 -0
- remap_badblocks/cli/commands/update.py +208 -0
- remap_badblocks/cli/commands/version.py +15 -0
- remap_badblocks/src/badblocks/_compute_good_ranges.py +43 -0
- remap_badblocks/src/badblocks/_find_badblocks.py +76 -0
- remap_badblocks/src/badblocks/_mapping_generation.py +12 -0
- remap_badblocks/src/badblocks/_remap_badblocks.py +114 -0
- remap_badblocks/src/badblocks/badblocks.py +40 -0
- remap_badblocks/src/devices/__init__.py +0 -0
- remap_badblocks/src/devices/device_config.py +62 -0
- remap_badblocks/src/devices/devices_config.py +300 -0
- remap_badblocks/src/devices/exceptions.py +22 -0
- remap_badblocks/src/devices_config_constants.py +3 -0
- remap_badblocks/src/mapping.py +109 -0
- remap_badblocks/src/remappers/_check_applied_devices.py +10 -0
- remap_badblocks/src/remappers/_generate_dm_table.py +27 -0
- remap_badblocks/src/test_utils.py +18 -0
- remap_badblocks/src/utils/__init__.py +0 -0
- remap_badblocks/src/utils/_get_device_info.py +43 -0
- remap_badblocks/src/utils/_iterable_bytes_converter.py +19 -0
- remap_badblocks/src/utils/_parse_inputs.py +84 -0
- remap_badblocks/src/utils/_run_command.py +76 -0
- remap_badblocks/src/utils/_sort_devices.py +26 -0
- remap_badblocks-0.7.dist-info/METADATA +130 -0
- remap_badblocks-0.7.dist-info/RECORD +66 -0
- remap_badblocks-0.7.dist-info/WHEEL +5 -0
- remap_badblocks-0.7.dist-info/entry_points.txt +2 -0
- remap_badblocks-0.7.dist-info/licenses/LICENSE +674 -0
- remap_badblocks-0.7.dist-info/top_level.txt +1 -0
- src/badblocks/_compute_good_ranges.py +43 -0
- src/badblocks/_find_badblocks.py +76 -0
- src/badblocks/_mapping_generation.py +12 -0
- src/badblocks/_remap_badblocks.py +114 -0
- src/badblocks/badblocks.py +40 -0
- src/devices/__init__.py +0 -0
- src/devices/device_config.py +62 -0
- src/devices/devices_config.py +300 -0
- src/devices/exceptions.py +22 -0
- src/devices_config_constants.py +3 -0
- src/mapping.py +109 -0
- src/remappers/_check_applied_devices.py +10 -0
- src/remappers/_generate_dm_table.py +27 -0
- src/test_utils.py +18 -0
- src/utils/__init__.py +0 -0
- src/utils/_get_device_info.py +43 -0
- src/utils/_iterable_bytes_converter.py +19 -0
- src/utils/_parse_inputs.py +84 -0
- src/utils/_run_command.py +76 -0
- src/utils/_sort_devices.py +26 -0
cli/commands/update.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
from argparse import Namespace
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from tempfile import NamedTemporaryFile
|
|
4
|
+
from typing import Iterable, Literal, Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
from remap_badblocks.src.badblocks._compute_good_ranges import (
|
|
7
|
+
compute_good_ranges, reserve_space_from_good_ranges)
|
|
8
|
+
from remap_badblocks.src.badblocks._find_badblocks import (
|
|
9
|
+
get_all_badblocks, read_known_badblocks)
|
|
10
|
+
from remap_badblocks.src.badblocks._mapping_generation import generate_mapping
|
|
11
|
+
from remap_badblocks.src.badblocks._remap_badblocks import (
|
|
12
|
+
count_used_spare_sectors, iter_all_spare_sectors, iter_free_spare_sectors,
|
|
13
|
+
remap_badblocks, simplify_mapping)
|
|
14
|
+
from remap_badblocks.src.devices.device_config import DeviceConfig
|
|
15
|
+
from remap_badblocks.src.devices.devices_config import DevicesConfig
|
|
16
|
+
from remap_badblocks.src.mapping import Mapping, MappingElement
|
|
17
|
+
from remap_badblocks.src.utils._parse_inputs import (
|
|
18
|
+
parse_bytes_to_sectors, parse_memory_number_to_bytes,
|
|
19
|
+
parse_memory_range_to_sectors)
|
|
20
|
+
from remap_badblocks.src.utils._run_command import pipe_lines_to_file
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UpdateArgs(TypedDict):
|
|
24
|
+
device_id: Optional[int]
|
|
25
|
+
device_name: Optional[str]
|
|
26
|
+
mode: Literal["read", "write", "skip"]
|
|
27
|
+
output: Optional[Path]
|
|
28
|
+
block_range: str
|
|
29
|
+
spare_space: Optional[str]
|
|
30
|
+
reset_mapping: bool
|
|
31
|
+
external_known_badblocks_file: Optional[Path]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_args(args: Namespace) -> UpdateArgs:
|
|
35
|
+
return {
|
|
36
|
+
"device_id": args.id,
|
|
37
|
+
"device_name": args.name,
|
|
38
|
+
"mode": args.mode,
|
|
39
|
+
"output": args.output,
|
|
40
|
+
"block_range": args.block_range,
|
|
41
|
+
"spare_space": (None if args.spare_space is None else args.spare_space.strip()),
|
|
42
|
+
"reset_mapping": args.reset_mapping,
|
|
43
|
+
"external_known_badblocks_file": args.known_badblocks_file,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_device_from_args(args: UpdateArgs, dc: DevicesConfig) -> DeviceConfig:
|
|
48
|
+
if args["device_id"] is None:
|
|
49
|
+
if args["device_name"] is not None:
|
|
50
|
+
return dc.get_device(name=args["device_name"])
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"Either --id or --name must be provided to update a device in the database."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return dc.get_device(id=args["device_id"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_update_block_range_from_args(
|
|
60
|
+
args: UpdateArgs, device: DeviceConfig
|
|
61
|
+
) -> tuple[int, int]:
|
|
62
|
+
start_block, end_block = parse_memory_range_to_sectors(
|
|
63
|
+
args["block_range"],
|
|
64
|
+
sector_size=device.sector_size,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (start_block is not None and start_block < device.logical_range[0]) or (
|
|
68
|
+
end_block is not None and end_block > device.logical_range[1]
|
|
69
|
+
):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Block range {start_block}-{end_block} must be contained"
|
|
72
|
+
f" within the logical range {device.logical_range[0]}-{device.logical_range[1]}."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if start_block is None:
|
|
76
|
+
start_block = device.logical_range[0]
|
|
77
|
+
if end_block is None:
|
|
78
|
+
end_block = device.logical_range[1]
|
|
79
|
+
|
|
80
|
+
return start_block, end_block
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_n_spare_sectors_from_args(args: UpdateArgs, device: DeviceConfig) -> int:
|
|
84
|
+
sector_size = device.sector_size
|
|
85
|
+
|
|
86
|
+
if args["spare_space"] is None:
|
|
87
|
+
spare_blocks = None
|
|
88
|
+
else:
|
|
89
|
+
spare_blocks = parse_bytes_to_sectors(
|
|
90
|
+
parse_memory_number_to_bytes(args["spare_space"], sector_size=sector_size),
|
|
91
|
+
sector_size,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if spare_blocks is not None:
|
|
95
|
+
return spare_blocks
|
|
96
|
+
else:
|
|
97
|
+
if device.spare_sectors is None:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"Should specify spare_sectors the first time you map badblocks"
|
|
100
|
+
)
|
|
101
|
+
return device.spare_sectors
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_starting_badblocks(args: UpdateArgs, device: DeviceConfig) -> set[int]:
|
|
105
|
+
known_badblocks: set[int] = set(device.badblocks)
|
|
106
|
+
|
|
107
|
+
# If known badblocks file is passed, merge it into our known_badblocks
|
|
108
|
+
if args["external_known_badblocks_file"]:
|
|
109
|
+
known_badblocks.update(
|
|
110
|
+
read_known_badblocks(
|
|
111
|
+
known_badblocks_file=args["external_known_badblocks_file"]
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return known_badblocks
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def get_current_badblocks(
|
|
119
|
+
args: UpdateArgs,
|
|
120
|
+
known_badblocks: set[int],
|
|
121
|
+
device: DeviceConfig,
|
|
122
|
+
block_range: tuple[int, int],
|
|
123
|
+
) -> set[int]:
|
|
124
|
+
if args["mode"] == "skip":
|
|
125
|
+
return known_badblocks
|
|
126
|
+
|
|
127
|
+
with NamedTemporaryFile() as known_badblocks_file:
|
|
128
|
+
known_badblocks_file.write("\n".join(map(str, known_badblocks)).encode("utf-8"))
|
|
129
|
+
known_badblocks_file.flush()
|
|
130
|
+
|
|
131
|
+
badblocks: set[int] = set(
|
|
132
|
+
get_all_badblocks(
|
|
133
|
+
device.path,
|
|
134
|
+
device.sector_size,
|
|
135
|
+
mode=args["mode"],
|
|
136
|
+
known_badblocks_file=Path(known_badblocks_file.name),
|
|
137
|
+
block_range=block_range,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if args["output"]:
|
|
142
|
+
pipe_lines_to_file(badblocks, args["output"])
|
|
143
|
+
|
|
144
|
+
return badblocks
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def update_mapping(
|
|
148
|
+
args: UpdateArgs,
|
|
149
|
+
device: DeviceConfig,
|
|
150
|
+
badblocks: Iterable[int],
|
|
151
|
+
n_spare_sectors: int,
|
|
152
|
+
) -> Mapping:
|
|
153
|
+
new_mapping: Iterable[tuple[int, int, int]]
|
|
154
|
+
|
|
155
|
+
if (device.mapping is None) or (args["reset_mapping"]):
|
|
156
|
+
good_ranges = compute_good_ranges(
|
|
157
|
+
badblocks, available_range=device.logical_range
|
|
158
|
+
)
|
|
159
|
+
good_ranges = reserve_space_from_good_ranges(good_ranges, n_spare_sectors)
|
|
160
|
+
good_ranges = reserve_space_from_good_ranges(
|
|
161
|
+
good_ranges,
|
|
162
|
+
int(100 * 1024 / device.sector_size),
|
|
163
|
+
)
|
|
164
|
+
new_mapping = generate_mapping(
|
|
165
|
+
good_ranges,
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
spare_sectors = iter_all_spare_sectors(n_spare_sectors, device.logical_range[0])
|
|
169
|
+
n_used_spare_sectors = count_used_spare_sectors(device.mapping, spare_sectors)
|
|
170
|
+
current_mapping = map(lambda x: x.to_tuple(), device.mapping)
|
|
171
|
+
new_mapping = remap_badblocks(
|
|
172
|
+
current_mapping,
|
|
173
|
+
badblocks,
|
|
174
|
+
spare_sectors=iter_free_spare_sectors(spare_sectors, n_used_spare_sectors),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
new_mapping = simplify_mapping(list(new_mapping))
|
|
178
|
+
return Mapping(set(map(MappingElement.from_tuple, new_mapping)))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def update(dc: DevicesConfig, _args: Namespace) -> None:
|
|
182
|
+
"""
|
|
183
|
+
Update the mapping of a device in the database.
|
|
184
|
+
"""
|
|
185
|
+
args = parse_args(_args)
|
|
186
|
+
|
|
187
|
+
device: DeviceConfig = get_device_from_args(args, dc)
|
|
188
|
+
block_range = get_update_block_range_from_args(args, device)
|
|
189
|
+
n_spare_sectors = get_n_spare_sectors_from_args(args, device)
|
|
190
|
+
|
|
191
|
+
known_badblocks = get_starting_badblocks(args, device)
|
|
192
|
+
badblocks = get_current_badblocks(args, known_badblocks, device, block_range)
|
|
193
|
+
|
|
194
|
+
print(
|
|
195
|
+
f"Badblocks computed. Badblocks went from {len(known_badblocks)} to {len(badblocks)}."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
dc.update_device(
|
|
199
|
+
id=device.id,
|
|
200
|
+
badblocks=badblocks,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
print("Badblocks list updated.")
|
|
204
|
+
|
|
205
|
+
new_mapping = update_mapping(args, device, badblocks, n_spare_sectors)
|
|
206
|
+
dc.update_device(id=device.id, mapping=new_mapping, spare_sectors=n_spare_sectors)
|
|
207
|
+
|
|
208
|
+
print("Mapping updated.")
|
cli/commands/version.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from tomllib import load
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_version() -> str:
|
|
6
|
+
|
|
7
|
+
pyproject = os.path.join(
|
|
8
|
+
os.path.dirname(os.path.abspath(__name__)), "pyproject.toml"
|
|
9
|
+
)
|
|
10
|
+
with open(pyproject, "rb") as f:
|
|
11
|
+
return load(f)["project"]["version"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def version():
|
|
15
|
+
print(f"remap-badblocks version v{get_version()}")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from argparse import ArgumentParser, Namespace
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from remap_badblocks.cli.commands import (add, apply, get, remove, update,
|
|
6
|
+
version)
|
|
7
|
+
from remap_badblocks.src.devices.devices_config import DevicesConfig
|
|
8
|
+
from remap_badblocks.src.devices_config_constants import \
|
|
9
|
+
DEFAULT_DEVICES_CONFIG_PATH
|
|
10
|
+
|
|
11
|
+
argparse = ArgumentParser(
|
|
12
|
+
description="Badblocks remapper for block devices.",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
argparse.add_argument(
|
|
16
|
+
"-P",
|
|
17
|
+
"--db-path",
|
|
18
|
+
type=Path,
|
|
19
|
+
default=DEFAULT_DEVICES_CONFIG_PATH,
|
|
20
|
+
help="Path to the devices configuration database.",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
subparser = argparse.add_subparsers(
|
|
24
|
+
title="Actions",
|
|
25
|
+
description="Available actions for the badblocks remapper.",
|
|
26
|
+
dest="action",
|
|
27
|
+
help="Action to perform",
|
|
28
|
+
required=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
add_new_device_parser = subparser.add_parser(
|
|
32
|
+
"add",
|
|
33
|
+
help="Add a new device to the database.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
add_new_device_parser.add_argument(
|
|
37
|
+
"name", type=str, help="The name of the output device."
|
|
38
|
+
)
|
|
39
|
+
add_new_device_parser.add_argument(
|
|
40
|
+
"--wwn",
|
|
41
|
+
type=str,
|
|
42
|
+
help="The WWN of the device to add.",
|
|
43
|
+
)
|
|
44
|
+
add_new_device_parser.add_argument(
|
|
45
|
+
"--path",
|
|
46
|
+
type=Path,
|
|
47
|
+
help="The path to the device to add.",
|
|
48
|
+
)
|
|
49
|
+
add_new_device_parser.add_argument(
|
|
50
|
+
"--depends-on",
|
|
51
|
+
type=str,
|
|
52
|
+
default=[],
|
|
53
|
+
action="append",
|
|
54
|
+
help=(
|
|
55
|
+
"The name of a device that this device depends on. Can be specified multiple times to add multiple"
|
|
56
|
+
" dependencies. This is used to ensure that devices are applied in the correct order."
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
add_new_device_parser.add_argument(
|
|
60
|
+
"--depends-on-id",
|
|
61
|
+
type=int,
|
|
62
|
+
default=[],
|
|
63
|
+
action="append",
|
|
64
|
+
help=(
|
|
65
|
+
"The ID of a device that this device depends on. Can be specified multiple times to add multiple"
|
|
66
|
+
" dependencies. This is used to ensure that devices are applied in the correct order."
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
add_new_device_parser.add_argument(
|
|
70
|
+
"--logical-sector-range",
|
|
71
|
+
type=str,
|
|
72
|
+
default="-",
|
|
73
|
+
help=(
|
|
74
|
+
"Logical sector range to assign to the device. Format: 'start-end', where 'end' can be omitted to mean"
|
|
75
|
+
" 'till the end of the device'. Both start and end can be specified as sector numbers or as byte offsets."
|
|
76
|
+
" In case of byte offsets, they must be multiples of the sector size. If not specified, the whole device"
|
|
77
|
+
" will be used. Note that end is not included in the range."
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
get_devices_parser = subparser.add_parser(
|
|
83
|
+
"get",
|
|
84
|
+
help="Get the existing device(s) in the database.",
|
|
85
|
+
)
|
|
86
|
+
get_devices_parser.add_argument(
|
|
87
|
+
"--id",
|
|
88
|
+
type=int,
|
|
89
|
+
default=None,
|
|
90
|
+
help="The ID of the device to get. If neither --id or --name are provided, all devices will be returned.",
|
|
91
|
+
)
|
|
92
|
+
get_devices_parser.add_argument(
|
|
93
|
+
"--name",
|
|
94
|
+
type=str,
|
|
95
|
+
default=None,
|
|
96
|
+
help="The name of the device to get. If neither --id or --name are provided, all devices will be returned.",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
remove_devices_parser = subparser.add_parser(
|
|
100
|
+
"remove",
|
|
101
|
+
help="Remove an existing device in the database.",
|
|
102
|
+
)
|
|
103
|
+
remove_devices_parser.add_argument(
|
|
104
|
+
"--id",
|
|
105
|
+
type=int,
|
|
106
|
+
default=None,
|
|
107
|
+
help="The ID of the device to remove. If neither --id or --name are provided, an error will be raised.",
|
|
108
|
+
)
|
|
109
|
+
remove_devices_parser.add_argument(
|
|
110
|
+
"--name",
|
|
111
|
+
type=str,
|
|
112
|
+
default=None,
|
|
113
|
+
help="The name of the device to remove. If neither --id or --name are provided, an error will be raised.",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
update_mapping_parser = subparser.add_parser(
|
|
117
|
+
"update",
|
|
118
|
+
help=(
|
|
119
|
+
"Update the mapping of a device in the database. More specifically: compute/update the device's badblocks and"
|
|
120
|
+
" store them in the database, compute the new mapping of a device; if the mapping has changed, the user will"
|
|
121
|
+
" be prompted to confirm the changes."
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
update_mapping_parser.add_argument(
|
|
125
|
+
"--id",
|
|
126
|
+
type=int,
|
|
127
|
+
default=None,
|
|
128
|
+
help="The ID of the device to update.",
|
|
129
|
+
)
|
|
130
|
+
update_mapping_parser.add_argument(
|
|
131
|
+
"--name",
|
|
132
|
+
type=str,
|
|
133
|
+
default=None,
|
|
134
|
+
help="The name of the device to update.",
|
|
135
|
+
)
|
|
136
|
+
update_mapping_parser.add_argument(
|
|
137
|
+
"--mode",
|
|
138
|
+
type=str,
|
|
139
|
+
choices=["read", "write", "skip"],
|
|
140
|
+
default="read",
|
|
141
|
+
help="Mode for computing badblocks",
|
|
142
|
+
)
|
|
143
|
+
update_mapping_parser.add_argument(
|
|
144
|
+
"--known-badblocks-file",
|
|
145
|
+
type=Path,
|
|
146
|
+
default=None,
|
|
147
|
+
help=(
|
|
148
|
+
"Path to file with known badblocks (a sector number for each line) that will be merged to those found so far."
|
|
149
|
+
" Take care the sector size is the same as configured into remap_badblocks"
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
update_mapping_parser.add_argument(
|
|
153
|
+
"--block-range",
|
|
154
|
+
type=str,
|
|
155
|
+
help=(
|
|
156
|
+
"Block range to check (e.g., 0-1000, or 1573-), omitted start and end means the whole logical range. Note"
|
|
157
|
+
" that the end is not included in the range"
|
|
158
|
+
),
|
|
159
|
+
default="-",
|
|
160
|
+
)
|
|
161
|
+
update_mapping_parser.add_argument(
|
|
162
|
+
"--output",
|
|
163
|
+
type=Path,
|
|
164
|
+
default=None,
|
|
165
|
+
help="Badblocks are stored internally. If provided, they will also be copied to this file.",
|
|
166
|
+
)
|
|
167
|
+
update_mapping_parser.add_argument(
|
|
168
|
+
"--spare-space",
|
|
169
|
+
type=str,
|
|
170
|
+
default=None,
|
|
171
|
+
help="Number of spare sectors to reserve from the good ranges. Cannot be changed after first time",
|
|
172
|
+
)
|
|
173
|
+
update_mapping_parser.add_argument(
|
|
174
|
+
"--reset-mapping",
|
|
175
|
+
action="store_true",
|
|
176
|
+
help=(
|
|
177
|
+
"If specified, will ignore existing mapping and build a new mapping from scratch."
|
|
178
|
+
" DANGER: this might lead to data loss, handle with care"
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
apply_devices_parser = subparser.add_parser(
|
|
183
|
+
"apply",
|
|
184
|
+
help="Apply a remapping that's already existing in the database.",
|
|
185
|
+
)
|
|
186
|
+
apply_devices_parser.add_argument(
|
|
187
|
+
"--id",
|
|
188
|
+
type=int,
|
|
189
|
+
default=None,
|
|
190
|
+
help="The ID of the device to apply. If neither --id or --name are provided, all devices will be applied.",
|
|
191
|
+
)
|
|
192
|
+
apply_devices_parser.add_argument(
|
|
193
|
+
"--name",
|
|
194
|
+
type=str,
|
|
195
|
+
default=None,
|
|
196
|
+
help="The name of the device to apply. If neither --id or --name are provided, all devices will be applied.",
|
|
197
|
+
)
|
|
198
|
+
apply_devices_parser.add_argument(
|
|
199
|
+
"--method",
|
|
200
|
+
type=str,
|
|
201
|
+
default="device-mapper",
|
|
202
|
+
choices=["device-mapper"],
|
|
203
|
+
help="Method to apply the mapping. Only device-mapper is available",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
version_parser = subparser.add_parser("version")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main() -> None:
|
|
210
|
+
args = argparse.parse_args()
|
|
211
|
+
db_path: Path = args.db_path
|
|
212
|
+
|
|
213
|
+
actions: dict[str, Callable[[DevicesConfig, Namespace], None]] = {
|
|
214
|
+
"add": add,
|
|
215
|
+
"apply": apply,
|
|
216
|
+
"get": get,
|
|
217
|
+
"remove": remove,
|
|
218
|
+
"update": update,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static_actions: dict[str, Callable[[], None]] = {
|
|
222
|
+
"version": version,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if args.action in actions:
|
|
226
|
+
if db_path.is_dir():
|
|
227
|
+
raise ValueError("The provided path must not be a directory.")
|
|
228
|
+
devices_config = DevicesConfig(db_path)
|
|
229
|
+
actions[args.action](devices_config, args)
|
|
230
|
+
elif args.action in static_actions:
|
|
231
|
+
static_actions[args.action]()
|
|
232
|
+
else:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"Unknown action: {args.action}. Please use one of {tuple(actions.keys()) + tuple(static_actions.keys())}."
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from argparse import Namespace
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, TypedDict
|
|
5
|
+
|
|
6
|
+
from remap_badblocks.src.devices.devices_config import DevicesConfig
|
|
7
|
+
from remap_badblocks.src.utils._get_device_info import (
|
|
8
|
+
get_disk_block_size, get_disk_number_of_blocks, resolve_device_name)
|
|
9
|
+
from remap_badblocks.src.utils._parse_inputs import \
|
|
10
|
+
parse_memory_range_to_sectors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AddArgs(TypedDict):
|
|
14
|
+
wwn: Optional[str]
|
|
15
|
+
path: Optional[Path]
|
|
16
|
+
name: str
|
|
17
|
+
depends_on_id: set[int]
|
|
18
|
+
depends_on: set[str]
|
|
19
|
+
logical_sector_range: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_args(args: Namespace) -> AddArgs:
|
|
23
|
+
return {
|
|
24
|
+
"wwn": args.wwn,
|
|
25
|
+
"path": args.path,
|
|
26
|
+
"name": args.name,
|
|
27
|
+
"depends_on_id": set(args.depends_on_id),
|
|
28
|
+
"depends_on": set(args.depends_on),
|
|
29
|
+
"logical_sector_range": args.logical_sector_range.strip(),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_path_from_args(args: AddArgs) -> Path:
|
|
34
|
+
wwn = args["wwn"]
|
|
35
|
+
path = args["path"]
|
|
36
|
+
|
|
37
|
+
if wwn is not None and path is not None:
|
|
38
|
+
raise ValueError("Only one of --wwn or --path can be provided, not both.")
|
|
39
|
+
|
|
40
|
+
if path is not None:
|
|
41
|
+
if isinstance(path, str):
|
|
42
|
+
path = Path(path)
|
|
43
|
+
return path
|
|
44
|
+
elif wwn is not None:
|
|
45
|
+
if not wwn.startswith("/dev/disk/by-id/"):
|
|
46
|
+
wwn = os.path.join("/dev/disk/by-id/", wwn)
|
|
47
|
+
return Path(wwn)
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError("Either --wwn or --path must be provided.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_device_dependencies_from_args(dc: DevicesConfig, args: AddArgs) -> set[int]:
|
|
53
|
+
return args["depends_on_id"] | {
|
|
54
|
+
dc.get_device(name=dep).id for dep in args["depends_on"] if dep
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_logical_block_range_from_args(
|
|
59
|
+
args: AddArgs, sector_size: int, total_sectors: int
|
|
60
|
+
) -> tuple[int, int]:
|
|
61
|
+
logical_start_block, logical_end_block = parse_memory_range_to_sectors(
|
|
62
|
+
args["logical_sector_range"], sector_size
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if logical_start_block is None:
|
|
66
|
+
logical_start_block = 0
|
|
67
|
+
if logical_end_block is None:
|
|
68
|
+
logical_end_block = total_sectors
|
|
69
|
+
|
|
70
|
+
return logical_start_block, logical_end_block
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def add(dc: DevicesConfig, _args: Namespace) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Add a new device to the database.
|
|
76
|
+
"""
|
|
77
|
+
args = parse_args(_args)
|
|
78
|
+
path: Path = get_path_from_args(args)
|
|
79
|
+
|
|
80
|
+
sector_size = get_disk_block_size(resolve_device_name(path))
|
|
81
|
+
total_sectors = get_disk_number_of_blocks(resolve_device_name(path))
|
|
82
|
+
|
|
83
|
+
dc.add_device(
|
|
84
|
+
path=path,
|
|
85
|
+
name=args["name"],
|
|
86
|
+
sector_size=sector_size,
|
|
87
|
+
depends_on=get_device_dependencies_from_args(dc, args),
|
|
88
|
+
logical_range=get_logical_block_range_from_args(
|
|
89
|
+
args, sector_size, total_sectors
|
|
90
|
+
),
|
|
91
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from argparse import Namespace
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Iterable, Literal, Optional, TypedDict
|
|
4
|
+
|
|
5
|
+
from remap_badblocks.src.devices.device_config import DeviceConfig
|
|
6
|
+
from remap_badblocks.src.devices.devices_config import DevicesConfig
|
|
7
|
+
from remap_badblocks.src.mapping import Mapping
|
|
8
|
+
from remap_badblocks.src.remappers._check_applied_devices import \
|
|
9
|
+
filter_applied_devices
|
|
10
|
+
from remap_badblocks.src.remappers._generate_dm_table import generate_dm_table
|
|
11
|
+
from remap_badblocks.src.utils._run_command import run_command_realtime
|
|
12
|
+
from remap_badblocks.src.utils._sort_devices import \
|
|
13
|
+
sort_devices_by_dependencies
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ApplyArgs(TypedDict):
|
|
17
|
+
device_id: Optional[int]
|
|
18
|
+
device_name: Optional[str]
|
|
19
|
+
method: Literal["device-mapper"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_args(args: Namespace) -> ApplyArgs:
|
|
23
|
+
return {
|
|
24
|
+
"device_id": args.id,
|
|
25
|
+
"device_name": args.name,
|
|
26
|
+
"method": args.method,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_user_wanted_device_list(
|
|
31
|
+
dc: DevicesConfig, args: ApplyArgs
|
|
32
|
+
) -> Iterable[DeviceConfig]:
|
|
33
|
+
if args["device_id"] is not None:
|
|
34
|
+
return [dc.get_device(id=args["device_id"])]
|
|
35
|
+
elif args["device_name"] is not None:
|
|
36
|
+
return [dc.get_device(name=args["device_name"])]
|
|
37
|
+
else:
|
|
38
|
+
return dc.get_devices()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def plan_apply_devices(
|
|
42
|
+
dc: DevicesConfig, devices: Iterable[DeviceConfig]
|
|
43
|
+
) -> Iterable[DeviceConfig]:
|
|
44
|
+
already_applied_devices = set(filter_applied_devices(dc.get_devices()))
|
|
45
|
+
return sort_devices_by_dependencies(
|
|
46
|
+
list(devices), already_applied_devices=already_applied_devices
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def apply_device_mapper(device: DeviceConfig):
|
|
51
|
+
mapping: Optional[Mapping] = device.mapping
|
|
52
|
+
sector_size: int = device.sector_size
|
|
53
|
+
device_path: Path = device.path
|
|
54
|
+
|
|
55
|
+
assert mapping is not None, "Call ‘remap_badblocks update‘ before applying"
|
|
56
|
+
|
|
57
|
+
device_dmtable_identifier = device_path
|
|
58
|
+
|
|
59
|
+
dmtable = "\n".join(
|
|
60
|
+
generate_dm_table(device_dmtable_identifier, mapping, sector_size)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for _ in run_command_realtime(["dmsetup", "create", device.name], stdin=dmtable):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def apply(dc: DevicesConfig, _args: Namespace):
|
|
68
|
+
args = parse_args(_args)
|
|
69
|
+
|
|
70
|
+
devices: Iterable[DeviceConfig] = get_user_wanted_device_list(dc, args)
|
|
71
|
+
devices = plan_apply_devices(dc, devices)
|
|
72
|
+
|
|
73
|
+
# TODO: remove already applied devices
|
|
74
|
+
|
|
75
|
+
method = args["method"]
|
|
76
|
+
assert (
|
|
77
|
+
method == "device-mapper"
|
|
78
|
+
), f"Only ‘device-mapper‘ method is available, found ‘{method}‘"
|
|
79
|
+
|
|
80
|
+
for device in devices:
|
|
81
|
+
apply_device_mapper(device)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from argparse import Namespace
|
|
2
|
+
from typing import Iterable, Optional
|
|
3
|
+
|
|
4
|
+
from remap_badblocks.src.devices.device_config import DeviceConfig
|
|
5
|
+
from remap_badblocks.src.devices.devices_config import DevicesConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_id_from_args(args: Namespace) -> Optional[int]:
|
|
9
|
+
return args.id
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_devices_to_print(
|
|
13
|
+
dc: DevicesConfig, id: Optional[int]
|
|
14
|
+
) -> Iterable[DeviceConfig]:
|
|
15
|
+
if id is not None:
|
|
16
|
+
return (dc.get_device(id=id),)
|
|
17
|
+
else:
|
|
18
|
+
return dc.get_devices()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get(dc: DevicesConfig, args: Namespace) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Get the configuration of a device from the database.
|
|
24
|
+
"""
|
|
25
|
+
id: Optional[int] = parse_id_from_args(args)
|
|
26
|
+
|
|
27
|
+
for device in get_devices_to_print(dc, id):
|
|
28
|
+
print(str(device))
|