Skip to content

Commit ea44119

Browse files
authored
Merge pull request #12 from apparentlyarhm/fix-efibootmgr-inconsistency
- add support for alternative EFI data record formats - add test mode functionality and debug output
2 parents eac3a58 + 1b20d5a commit ea44119

File tree

1 file changed

+119
-29
lines changed

1 file changed

+119
-29
lines changed

rename-efi-entry.bash

Lines changed: 119 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ print_usage_and_efi_data () {
4343
efibootmgr --verbose
4444
}
4545

46+
# function to print debug messages if test_mode is enabled
47+
# usage: debug "your message here"
48+
debug() {
49+
if [[ $test_mode -eq 1 ]]; then
50+
echo "DEBUG: $*" >&2
51+
fi
52+
}
53+
54+
# The reason we are doing is because it seems that efibootmgr output differ slightly across different distros.
55+
# Arch and Fedora do not have the `File` prefix in the loader path, while Ubuntu and Mint does.
56+
57+
# standard efibootmgr (most common): ends with loader path
58+
REGEX_LOADER='^Boot([[:xdigit:]]{4})\*?[[:blank:]]+(.+)[[:blank:]]+HD\(([[:digit:]]+),[^,]+,([^,]+)[^\)]+\)\/(.+)$'
59+
60+
# alternative format: loader path wrapped in File(...)
61+
REGEX_LOADER_FILE='^Boot([[:xdigit:]]{4})\*?[[:blank:]]+(.+)[[:blank:]]+HD\(([[:digit:]]+),[^,]+,([^,]+)[^\)]+\)\/File\(([^\)]+)\)$'
62+
63+
# sfdisk format for extracting device and partition uuid
64+
# e.g. /dev/sda1 : start=..., size=..., ..., uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462
65+
REGEX_SFDISK_UUID='^([^[:blank:]]+)[[:blank:]]:[[:blank:]].*[[:blank:]]uuid=([^,]+)'
66+
67+
REGEX_UUID='^(/dev/(sd[a-z]|nvme[[:digit:]]+n[[:digit:]]+|mmcblk[[:digit:]]+))p?([[:digit:]]+)$'
68+
# ----------------------------------------------------------------------------------------------------------------------
69+
4670
# script start ---------------------------------------------------------------------------------------------------------
4771

4872
# check whether we run as root
@@ -68,31 +92,50 @@ old_label="$1"
6892
new_label="$2"
6993
old_bootnum="$3"
7094

95+
# default: execute normally
96+
test_mode=0
97+
98+
# parse remaining arguments for optional flags
99+
for arg in "$@"; do
100+
if [[ "$arg" == "--test" ]]; then
101+
test_mode=1
102+
fi
103+
done
104+
105+
if [[ $test_mode -eq 1 ]]; then
106+
echo "$0 : INFO : test mode enabled; no commands will be executed, only verification and dry run"
107+
fi
108+
109+
71110
# obtain disk device names as `disk_names` array -----------------------------------------------------------------------
72111

73112
# obtain disk data as long text
74113
disk_data_all=$(lsblk --nodeps --noheadings --pairs | grep 'TYPE="disk"')
75114

76115
# split disk data into a string array
116+
debug "Finding disk devices..."
77117
readarray -t disk_data_array <<<"$disk_data_all"
78118

79119
# obtain an array of disk names
80120
disk_names=()
81121
for disk_data_line in "${disk_data_array[@]}" ; do
82122
if [[ $disk_data_line =~ ^NAME=\"([^\"]+)\" ]] ; then
83123
disk_names+=(${BASH_REMATCH[1]})
124+
debug "Found disk: ${BASH_REMATCH[1]}"
84125
fi
85126
done
86127
# ----------------------------------------------------------------------------------------------------------------------
87128

88129
# obtain an associative array of devices against partition uuid values -------------------------------------------------
130+
debug "Mapping partition UUIDs to device names..."
89131

90132
# the associative array to be filled
91133
# data example : partitions['2ffcc127-f6ce-40f0-9932-d1dfd14e9462']='/dev/sda1'
92134
declare -A partitions
93135

94136
# loop over all disk devices
95137
for disk_name in "${disk_names[@]}" ; do
138+
debug "Scanning disk /dev/$disk_name for partitions..."
96139
# obtain partition data as long text, suppressing possible error messages in stderr,
97140
# e.g. "sfdisk: /dev/sdb: does not contain a recognized partition table"
98141
# NOTE sfdisk call requires `sudo`
@@ -107,42 +150,78 @@ for disk_name in "${disk_names[@]}" ; do
107150
# /dev/sda1 : start= 2048, size= 1048576, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462, name="EFI System Partition"
108151
# /dev/sda1 : start= 2048, size= 1048576, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462
109152
for partition_data_line in "${partition_data_array[@]}" ; do
110-
if [[ $partition_data_line =~ ^([^[:blank:]]+)[[:blank:]]:[[:blank:]].*[[:blank:]]uuid=([^,]+) ]] ; then
153+
if [[ $partition_data_line =~ $REGEX_SFDISK_UUID ]] ; then
111154
device=${BASH_REMATCH[1]}
112155
uuid_lowercase="${BASH_REMATCH[2],,}"
113156
partitions[$uuid_lowercase]=$device
157+
debug "Mapped UUID $uuid_lowercase to $device"
158+
114159
fi
115160
done
116161
done
162+
if [ ${#partitions[@]} -eq 0 ]; then
163+
echo "$0 : WARNING : Could not find any partitions with UUIDs via sfdisk."
164+
fi
117165
# ----------------------------------------------------------------------------------------------------------------------
118166

119167
# obtain EFI data ------------------------------------------------------------------------------------------------------
168+
debug "Scanning EFI boot entries..."
120169

121170
# obtain EFI data as long text
122171
efi_data_all=$(efibootmgr --verbose)
123172

124173
# split EFI data into a string array
125174
readarray -t efi_data_array <<<"$efi_data_all"
126175

176+
target_bootnum=""
177+
target_part=""
178+
target_uuid=""
179+
target_loader=""
180+
127181
# obtain EFI data for a matching label
128182
for efi_data_line in "${efi_data_array[@]}" ; do
129-
if [[ $efi_data_line =~ ^Boot([[:xdigit:]]{4})\*?[[:blank:]]+(.+)[[:blank:]]+HD\(([[:digit:]]+),[^,]+,([^,]+)[^\)]+\)/File\(([^\)]+)\) ]] ; then
130-
label="${BASH_REMATCH[2]}"
131-
if [ "$label" = "$old_label" ] || [ "$old_label" = '*' ] ; then
132-
if [ -z "$target_bootnum" ] ; then # no `bootnum` match or candidate found yet
133-
if [ -z "$old_bootnum" ] || [ ${BASH_REMATCH[1]} = "$old_bootnum" ] ; then
134-
target_bootnum=${BASH_REMATCH[1]}
135-
target_part=${BASH_REMATCH[3]}
136-
target_uuid=${BASH_REMATCH[4]}
137-
target_loader=${BASH_REMATCH[5]}
138-
fi
139-
else # `bootnum` match or candidate already found
140-
if [ -z "$old_bootnum" ] ; then
141-
echo "ERROR: more than one boot entry found with label matching '$old_label': $target_bootnum and ${BASH_REMATCH[1]};"
142-
echo " please use optional 'bootnum' command line argument to resolve this ambiguity."
143-
exit 1
144-
fi
183+
184+
# These will be set if a line matches one of our regexes
185+
line_bootnum="" line_label="" line_part="" line_uuid="" line_loader=""
186+
187+
# Try to match the line against known formats
188+
if [[ $efi_data_line =~ $REGEX_LOADER ]]; then
189+
line_bootnum="${BASH_REMATCH[1]}"
190+
line_label="${BASH_REMATCH[2]}"
191+
line_part="${BASH_REMATCH[3]}"
192+
line_uuid="${BASH_REMATCH[4],,}" # lowercase for consistency
193+
line_loader="${BASH_REMATCH[5]}"
194+
debug "Parsed entry: BootNum=$line_bootnum, Label='$line_label' (standard format)"
195+
196+
elif [[ $efi_data_line =~ $REGEX_LOADER_FILE ]]; then
197+
line_bootnum="${BASH_REMATCH[1]}"
198+
line_label="${BASH_REMATCH[2]}"
199+
line_part="${BASH_REMATCH[3]}"
200+
line_uuid="${BASH_REMATCH[4],,}" # lowercase for consistency
201+
line_loader="${BASH_REMATCH[5]}"
202+
debug "Parsed entry: BootNum=$line_bootnum, Label='$line_label' (File() format)"
203+
204+
else
205+
debug "Line did not match any known EFI entry format, skipping: $efi_data_line"
206+
continue
207+
fi
208+
209+
# Now check if this parsed line is the one we are looking for
210+
if [[ "$old_label" == "$line_label" || "$old_label" == "*" ]]; then
211+
# Label matches. Now check if boot number matches (if one was provided).
212+
if [[ -z "$old_bootnum" || "$old_bootnum" == "$line_bootnum" ]]; then
213+
# This is a match. Check for ambiguity.
214+
if [[ -n "$target_bootnum" ]]; then
215+
echo "$0 : ERROR : more than one boot entry found matching label '$old_label'." >&2
216+
echo " Found $target_bootnum and now $line_bootnum. Please use the optional 'bootnum' argument to specify which one." >&2
217+
exit 1
145218
fi
219+
# This is our first and only match so far. Store its details.
220+
debug "Found a matching target entry: Boot$line_bootnum"
221+
target_bootnum="$line_bootnum"
222+
target_part="$line_part"
223+
target_uuid="$line_uuid"
224+
target_loader="$line_loader"
146225
fi
147226
fi
148227
done
@@ -153,21 +232,23 @@ if [ -z "$target_bootnum" ] ; then
153232
echo "$0 : ERROR : no EFI data found for any label matching '$old_label'."
154233
exit 1
155234
fi
235+
debug "Target found: BootNum=$target_bootnum, Part=$target_part, UUID=$target_uuid, Loader=$target_loader"
156236

157237
# obtain device for the partition with uuid that corresponds to the given label
158238
device_for_uuid=${partitions[$target_uuid]}
159239
if [ -z "$device_for_uuid" ] ; then
160240
echo "$0 : ERROR : EFI label '$old_label' is related to partition '$target_uuid' that is not currently known to the system."
161241
exit 1
162242
fi
243+
debug "Partition UUID $target_uuid corresponds to device $device_for_uuid"
163244

164245
# verify that device/partition name matches some expected pattern;
165246
# the following partition name patterns/samples are recognized:
166247
# - SCSI family : e.g. /dev/sda1
167248
# - NVMe : e.g. /dev/nvme0n1p1
168249
# - MMC family : e.g. /dev/mmcblk0p1
169250
# see https://wiki.archlinux.org/index.php/Device_file#Block_device_names
170-
if [[ $device_for_uuid =~ ^(/dev/(sd[a-z]|nvme[[:digit:]]+n[[:digit:]]+|mmcblk[[:digit:]]+))p?([[:digit:]]+)$ ]] ; then
251+
if [[ $device_for_uuid =~ $REGEX_UUID ]] ; then
171252
device_name=${BASH_REMATCH[1]}
172253
device_part=${BASH_REMATCH[3]}
173254
else
@@ -176,27 +257,36 @@ else
176257
fi
177258

178259
# verify that partition number of the device matches partition number in the EFI entry
179-
if [[ $device_part != $target_part ]] ; then
260+
if [[ $device_part != "$target_part" ]] ; then
180261
echo "$0 : ERROR : partition number of the device [$device_part] is different from partition number in the EFI entry [$target_part]."
181262
exit 1
182263
fi
183264

184265
# prepare efibootmgr commands to be executed
185-
printf -v escaped_loader "%q" $target_loader
266+
printf -v escaped_loader "%q" "$target_loader"
186267
efi_command_1="efibootmgr --bootnum $target_bootnum --delete-bootnum"
187268
efi_command_2="efibootmgr --create --disk $device_name --part $target_part --label '$new_label' --loader $escaped_loader"
188269

189270
# obtain final user consent and apply modifications
190271
echo The following commands are about to be executed:
191272
echo " $efi_command_1"
192273
echo " $efi_command_2"
193-
read -p "Execute these commands? [y/N] " -n 1 -r
194-
echo # terminate the shell UI line
195-
if [[ $REPLY =~ ^[Yy]$ ]] ; then
196-
echo "... executing \`$efi_command_1\` ..."
197-
eval $efi_command_1
198-
echo "... executing \`$efi_command_2\` ..."
199-
eval $efi_command_2
274+
275+
if [[ $test_mode -eq 1 ]]; then
276+
echo "$0 : INFO : --test mode enabled. No changes will be made."
277+
echo "$0 : INFO : Script validation complete. Ready to rename EFI entry."
200278
else
201-
echo "$0 : INFO : command execution aborted"
202-
fi
279+
read -p "Execute these commands? [y/N] " -n 1 -r
280+
echo # terminate the shell UI line
281+
if [[ $REPLY =~ ^[Yy]$ ]] ; then
282+
echo "... executing \`$efi_command_1\` ..."
283+
eval "$efi_command_1"
284+
echo "... executing \`$efi_command_2\` ..."
285+
eval "$efi_command_2"
286+
echo
287+
echo "Done. Verifying new entry:"
288+
efibootmgr | grep --color=always "$new_label"
289+
else
290+
echo "$0 : INFO : Command execution aborted by user."
291+
fi
292+
fi

0 commit comments

Comments
 (0)