-
Notifications
You must be signed in to change notification settings - Fork 246
/
300_map_disks.sh
248 lines (233 loc) · 13.1 KB
/
300_map_disks.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# Map source disks (i.e. 'disk' devices and 'multipath' devices in the LAYOUT_FILE)
# to target disks (i.e. current devices in the currently running recovery system).
# Skip if not in migration mode:
is_true "$MIGRATION_MODE" || return 0
generate_layout_dependencies
mapping_file_basename="disk_mappings"
MAPPING_FILE="$VAR_DIR/layout/$mapping_file_basename"
# Local functions that are 'unset' at the end of this script:
# Add a disk mapping from source $1 to target $2.
# Source and target should be like "sda" "cciss/c1d0".
function add_mapping () {
echo "$1 $2" >>"$MAPPING_FILE"
}
# Return 0 if a mapping for $1 exists
# i.e. if $1 is used as source in the mapping file
# (grep returns 0 if found and 1 or 2 otherwise):
function is_mapping_source () {
grep -q "^$1 " "$MAPPING_FILE"
}
# Return 0 if $1 is used as a target in a mapping
# (grep returns 0 if found and 1 or 2 otherwise):
function is_mapping_target () {
grep -q " $1$" "$MAPPING_FILE"
}
# Start with an empty mapping file unless there is a user provided mapping file.
# If etc/rear/mappings/disk_mappings or etc/rear/mappings/disk_devices exists
# use that as mapping file where etc/rear/mappings/disk_mappings is preferred
# and etc/rear/mappings/disk_devices is only there for backward compatibility:
cat /dev/null >"$MAPPING_FILE"
for user_provided_mapping_file in $mapping_file_basename disk_devices ; do
if test -f "$user_provided_mapping_file" ; then
cp "$user_provided_mapping_file" "$MAPPING_FILE"
LogPrint "Using user provided mapping file $user_provided_mapping_file"
break
fi
done
# Automap original 'disk' devices and 'multipath' devices in the LAYOUT_FILE
# to current block devices in the currently running recovery system:
while read keyword orig_device orig_size junk ; do
# Continue with next original device when it is already used as source in the mapping file:
is_mapping_source "$orig_device" && continue
# First, try to find if there is a current disk with same name and same size as the original:
sysfs_device_name="$( get_sysfs_name "$orig_device" )"
current_device="/sys/block/$sysfs_device_name"
if test -e $current_device ; then
current_size=$( get_disk_size $sysfs_device_name )
# The current_device (e.g. /sys/block/sda) is not a block device so that
# its matching actual block device (e.g. /dev/sda) must be determined:
preferred_target_device_name="$( get_device_name $current_device )"
# Continue with next one if the current one is already used as target in the mapping file:
is_mapping_target "$preferred_target_device_name" && continue
# Use the current one if it is of same size as the old one:
if test "$orig_size" -eq "$current_size" ; then
# Ensure the determined target device is really a block device:
if test -b "$preferred_target_device_name" ; then
add_mapping "$orig_device" "$preferred_target_device_name"
LogPrint "Using $preferred_target_device_name (same name and same size) for recreating $orig_device"
# Continue with next original device in the LAYOUT_FILE:
continue
fi
fi
fi
# Else, loop over all current block devices to find one of the same size as the original:
for current_device_path in /sys/block/* ; do
# Continue with next block device if the current one has no queue directory:
test -d $current_device_path/queue || continue
# Continue with next block device if no size can be read for the current one:
test -r $current_device_path/size || continue
current_disk_name="${current_device_path#/sys/block/}"
current_size=$( get_disk_size $current_disk_name )
# The current_device_path (e.g. /sys/block/sdb) is not a block device so that
# its matching actual block device (e.g. /dev/sdb) must be determined:
preferred_target_device_name="$( get_device_name $current_device_path )"
# Continue with next one if the current one is already used as target in the mapping file:
is_mapping_target "$preferred_target_device_name" && continue
# Use the current one if it is of same size as the old one:
if test "$orig_size" -eq "$current_size" ; then
# Ensure the determined target device is really a block device:
if test -b "$preferred_target_device_name" ; then
add_mapping "$orig_device" "$preferred_target_device_name"
LogPrint "Using $preferred_target_device_name (same size) for recreating $orig_device"
# Break looping over all current block devices to find one
# and continue with next original device in the LAYOUT_FILE:
break
fi
fi
done
done < <( grep -E "^disk |^multipath " "$LAYOUT_FILE" )
# For every unmapped original 'disk' device and 'multipath' device in the LAYOUT_FILE
# let the user choose from the still unmapped disks in the currently running recovery system:
while read keyword orig_device orig_size junk ; do
# Continue with next original device when it is already used as source in the mapping file
# i.e. when it is already mapped to one in the currently running recovery system:
is_mapping_source "$orig_device" && continue
# Inform the user about the unmapped original device:
preferred_orig_device_name="$( get_device_name $orig_device )"
LogUserOutput "Original disk $preferred_orig_device_name does not exist (with same size) in the target system"
# Build the set of still unmapped current disks wherefrom the user can choose:
possible_targets=()
# Loop over all current block devices to find appropriate ones wherefrom the user can choose:
for current_device_path in /sys/block/* ; do
current_device_basename="${current_device_path##*/}"
# Do not include removable devices in the choices for the user:
if test "$( < $current_device_path/removable )" = "1" ; then
Log "$current_device_basename excluded from device mapping choices (is a removable device)"
continue
fi
# Do not include devices in EXCLUDE_DEVICE_MAPPING in the choices for the user.
# One cannot use IsInArray here because EXCLUDE_DEVICE_MAPPING contains patterns
# (e.g. "loop*" and "ram*" see default.conf) so that 'case' pattern matching is used:
for pattern in "${EXCLUDE_DEVICE_MAPPING[@]}" ; do
case "$current_device_basename" in
($pattern)
Log "$current_device_basename excluded from device mapping choices (matches '$pattern' in EXCLUDE_DEVICE_MAPPING)"
# Continue with next block device:
continue 2
;;
esac
done
preferred_target_device_name="$( get_device_name $current_device_path )"
# Continue with next block device if the current one has no queue directory:
if ! test -d $current_device_path/queue ; then
Log "$preferred_target_device_name excluded from device mapping choices (has no queue directory)"
continue
fi
# Continue with next block device if the current one is already used as target in the mapping file:
if is_mapping_target "$preferred_target_device_name" ; then
Log "$preferred_target_device_name excluded from device mapping choices (is already used as mapping target)"
continue
fi
# Add the current device as possible choice for the user:
possible_targets=( "${possible_targets[@]}" "$preferred_target_device_name" )
done
# Continue with next original device when no appropriate current block device is found whereto it could be mapped:
if ! test "${possible_targets[*]}" ; then
LogUserOutput "No device found whereto $preferred_orig_device_name could be mapped so that it will not be recreated"
continue
fi
# Automatically map when only one appropriate current block device is found whereto it could be mapped.
# At the end the mapping file is shown and the user can edit it if he does not like an automated mapping:
if test "1" -eq "${#possible_targets[@]}" ; then
add_mapping "$orig_device" "$possible_targets"
LogPrint "Using $possible_targets (the only appropriate) for recreating $orig_device"
# Continue with next original device in the LAYOUT_FILE:
continue
fi
# Show the appropriate current block devices and let the user choose:
skip_choice="Do not map $preferred_orig_device_name"
regular_choices=( "${possible_targets[@]}" "$skip_choice" )
rear_shell_choice="Use Relax-and-Recover shell and return back to here"
prompt="Choose an appropriate replacement for $preferred_orig_device_name"
choice=""
wilful_input=""
# Generate a runtime-specific user_input_ID so that for each unmapped original device
# a different user_input_ID is used for the UserInput call so that the user can specify
# for each unmapped original device a different predefined user input.
# Only uppercase letters and digits are used to ensure the user_input_ID is a valid bash variable name
# (otherwise the UserInput call could become invalid which aborts 'rear recover' with a BugError) and
# hopefully only uppercase letters and digits are sufficient to distinguish different devices:
current_orig_device_basename_alnum_uppercase="$( basename "$preferred_orig_device_name" | tr -d -c '[:alnum:]' | tr '[:lower:]' '[:upper:]' )"
test "$current_orig_device_basename_alnum_uppercase" || current_orig_device_basename_alnum_uppercase="DISK"
user_input_ID="LAYOUT_MIGRATION_REPLACEMENT_$current_orig_device_basename_alnum_uppercase"
until IsInArray "$choice" "${regular_choices[@]}" ; do
# Default input is the first regular choice which is the first of the possible targets:
choice="$( UserInput -I $user_input_ID -p "$prompt" -D 1 "${regular_choices[@]}" "$rear_shell_choice" )" && wilful_input="yes" || wilful_input="no"
test "$rear_shell_choice" = "$choice" && rear_shell
done
# Continue with next original device when the user selected to not map it:
if test "$skip_choice" = "$choice" ; then
LogUserOutput "No mapping for $preferred_orig_device_name so that it will not be recreated"
continue
fi
# Use what the user selected:
add_mapping "$orig_device" "$choice"
if is_true "$wilful_input" ; then
LogUserOutput "Using $choice (chosen by user) for recreating $orig_device"
else
LogUserOutput "Using $choice (default choice) for recreating $orig_device"
fi
done < <( grep -E "^disk |^multipath " "$LAYOUT_FILE" )
# Show the mappings to the user and let him confirm the mappings
# or let him edit the mapping file as he actually needs:
rear_workflow="rear $WORKFLOW"
rear_shell_history="$( echo -e "vi $MAPPING_FILE\nless $MAPPING_FILE" )"
unset choices
choices[0]="Confirm disk mapping and continue '$rear_workflow'"
choices[1]="Edit disk mapping ($MAPPING_FILE)"
choices[2]="Use Relax-and-Recover shell and return back to here"
choices[3]="Abort '$rear_workflow'"
prompt="Confirm or edit the disk mapping"
choice=""
wilful_input=""
# When USER_INPUT_LAYOUT_MIGRATION_CONFIRM_MAPPINGS has any 'true' value be liberal in what you accept and
# assume choices[0] 'Confirm mapping' was actually meant:
is_true "$USER_INPUT_LAYOUT_MIGRATION_CONFIRM_MAPPINGS" && USER_INPUT_LAYOUT_MIGRATION_CONFIRM_MAPPINGS="${choices[0]}"
while true ; do
LogUserOutput 'Current disk mapping table (source -> target):'
LogUserOutput "$( sed -e 's|^| |' "$MAPPING_FILE" )"
choice="$( UserInput -I LAYOUT_MIGRATION_CONFIRM_MAPPINGS -p "$prompt" -D "${choices[0]}" "${choices[@]}" )" && wilful_input="yes" || wilful_input="no"
case "$choice" in
(${choices[0]})
# Continue recovery:
is_true "$wilful_input" && LogPrint "User confirmed disk mapping" || LogPrint "Continuing '$rear_workflow' by default"
break
;;
(${choices[1]})
# Run 'vi' with the original STDIN STDOUT and STDERR when 'rear' was launched by the user:
vi $MAPPING_FILE 0<&6 1>&7 2>&8
;;
(${choices[2]})
# rear_shell runs 'bash' with the original STDIN STDOUT and STDERR when 'rear' was launched by the user:
rear_shell "" "$rear_shell_history"
;;
(${choices[3]})
abort_recreate
Error "User chose to abort '$rear_workflow' in ${BASH_SOURCE[0]}"
;;
esac
done
# Mark unmapped 'disk' devices and 'multipath' devices in the LAYOUT_FILE as done
# so that unmapped 'disk' and 'multipath' devices will not be recreated:
while read keyword device junk ; do
if ! is_mapping_source "$device" ; then
LogUserOutput "Disk $device and all dependant devices will not be recreated"
mark_as_done "$device"
mark_tree_as_done "$device"
fi
done < <( grep -E "^disk |^multipath " "$LAYOUT_FILE" )
# Local functions must be 'unset' because bash does not support 'local function ...'
# cf. https://unix.stackexchange.com/questions/104755/how-can-i-create-a-local-function-in-my-bashrc
unset -f add_mapping
unset -f is_mapping_source
unset -f is_mapping_target