Skip to content

Commit 33b3970

Browse files
juliakregerelfosardo
authored andcommitted
Delete EFI boot entry duplicate labels first
Some firmware seems to take an objection with EFI nvram entries being deleted after one is added, resulting in the entire entry table being reset to the last known good state. This is problematic, as ultimately deployments can time out if we previously booted with Networking, and the machine, while commanded to do other wise, reboots back to networking regardless. We will now delete entries first, before proceeding. Additionally, for general use, this pattern may serve the community better by avoiding cases where we would have previously just relied upon efibootmgr[0] to warn us of duplicate entries. [0]: https://github.com/rhboot/efibootmgr/blob/103aa22ece98f09fe3ea2a0c83988f0ee2d0e5a8/src/efibootmgr.c#L228 Change-Id: Ib61a7100a059e79a8b0901fd8f46b9bc41d657dc Story: 2009649 Task: 43808 (cherry picked from commit 67eddfa)
1 parent cc79140 commit 33b3970

File tree

3 files changed

+51
-24
lines changed

3 files changed

+51
-24
lines changed

ironic_python_agent/extensions/image.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -274,12 +274,10 @@ def _run_efibootmgr(valid_efi_bootloaders, device, efi_partition,
274274

275275
# Before updating let's get information about the bootorder
276276
LOG.debug("Getting information about boot order.")
277-
utils.execute('efibootmgr', '-v')
278-
# NOTE(iurygregory): regex used to identify the Warning in the stderr after
279-
# we add the new entry. Example:
280-
# "efibootmgr: ** Warning ** : Boot0004 has same label ironic"
281-
duplicated_label = re.compile(r'^.*:\s\*\*.*\*\*\s:\s.*'
282-
r'Boot([0-9a-f-A-F]+)\s.*$')
277+
original_efi_output = utils.execute('efibootmgr', '-v')
278+
# NOTE(TheJulia): regex used to identify entries in the efibootmgr
279+
# output on stdout.
280+
entry_label = re.compile(r'Boot([0-9a-f-A-F]+):\s(.*).*$')
283281
label_id = 1
284282
for v_bl in valid_efi_bootloaders:
285283
if 'csv' in v_bl.lower():
@@ -298,20 +296,26 @@ def _run_efibootmgr(valid_efi_bootloaders, device, efi_partition,
298296
v_efi_bl_path = '\\' + v_bl.replace('/', '\\')
299297
label = 'ironic' + str(label_id)
300298

299+
# Iterate through standard out, and look for duplicates
300+
for line in original_efi_output[0].split('\n'):
301+
match = entry_label.match(line)
302+
# Look for the base label in the string if a line match
303+
# occurs, so we can identify if we need to eliminate the
304+
# entry.
305+
if match and label in match.group(2):
306+
boot_num = match.group(1)
307+
LOG.debug("Found bootnum %s matching label", boot_num)
308+
utils.execute('efibootmgr', '-b', boot_num, '-B')
309+
301310
LOG.debug("Adding loader %(path)s on partition %(part)s of device "
302311
" %(dev)s", {'path': v_efi_bl_path, 'part': efi_partition,
303312
'dev': device})
304313
# Update the nvram using efibootmgr
305314
# https://linux.die.net/man/8/efibootmgr
306-
cmd = utils.execute('efibootmgr', '-v', '-c', '-d', device,
307-
'-p', efi_partition, '-w', '-L', label,
308-
'-l', v_efi_bl_path)
309-
for line in cmd[1].split('\n'):
310-
match = duplicated_label.match(line)
311-
if match:
312-
boot_num = match.group(1)
313-
LOG.debug("Found bootnum %s matching label", boot_num)
314-
utils.execute('efibootmgr', '-b', boot_num, '-B')
315+
utils.execute('efibootmgr', '-v', '-c', '-d', device,
316+
'-p', efi_partition, '-w', '-L', label,
317+
'-l', v_efi_bl_path)
318+
# Increment the ID in case the loop runs again.
315319
label_id += 1
316320

317321

ironic_python_agent/tests/unit/extensions/test_image.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,17 @@ def test__uefi_bootloader_with_entry_removal(
319319
mock_partition.return_value = self.fake_dev
320320
mock_utils_efi_part.return_value = '1'
321321
mock_efi_bl.return_value = ['EFI/BOOT/BOOTX64.EFI']
322-
stdeer_msg = """
323-
efibootmgr: ** Warning ** : Boot0004 has same label ironic1\n
324-
efibootmgr: ** Warning ** : Boot0005 has same label ironic1\n
325-
"""
322+
stdout_msg = """
323+
BootCurrent: 0001
324+
Timeout: 0 seconds
325+
BootOrder: 0000,00001
326+
Boot0000: ironic1 HD(1, GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI)
327+
Boot0001: ironic2 HD(1, GPT,4f3c6294-bf9b-4208-9808-111111111112)File(\EFI\Boot\BOOTX64.EFI)
328+
Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51)
329+
""" # noqa This is a giant literal string for testing.
326330
mock_execute.side_effect = iter([('', ''), ('', ''),
327331
('', ''), ('', ''),
328-
('', ''), ('', stdeer_msg),
332+
(stdout_msg, ''), ('', ''),
329333
('', ''), ('', ''),
330334
('', ''), ('', '')])
331335

@@ -336,12 +340,11 @@ def test__uefi_bootloader_with_entry_removal(
336340
mock.call('mount', self.fake_efi_system_part,
337341
self.fake_dir + '/boot/efi'),
338342
mock.call('efibootmgr', '-v'),
343+
mock.call('efibootmgr', '-b', '0000', '-B'),
339344
mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
340345
'-p', '1', '-w',
341346
'-L', 'ironic1', '-l',
342347
'\\EFI\\BOOT\\BOOTX64.EFI'),
343-
mock.call('efibootmgr', '-b', '0004', '-B'),
344-
mock.call('efibootmgr', '-b', '0005', '-B'),
345348
mock.call('umount', self.fake_dir + '/boot/efi',
346349
attempts=3, delay_on_retry=True),
347350
mock.call('sync')]
@@ -356,7 +359,7 @@ def test__uefi_bootloader_with_entry_removal(
356359
mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
357360
mock_execute.assert_has_calls(expected)
358361
mock_utils_efi_part.assert_called_once_with(self.fake_dev)
359-
self.assertEqual(10, mock_execute.call_count)
362+
self.assertEqual(9, mock_execute.call_count)
360363

361364
@mock.patch.object(hardware, 'is_md_device', lambda *_: False)
362365
@mock.patch.object(os.path, 'exists', lambda *_: False)
@@ -2259,8 +2262,19 @@ def test__manage_uefi_found_csv(self, mkdir_mock, mock_utils_efi_part,
22592262
# Mild difference, Ubuntu ships a file without a 0xFEFF delimiter
22602263
# at the start of the file, where as Red Hat *does*
22612264
csv_file_data = u'shimx64.efi,Vendor String,,Grub2MadeUSDoThis\n'
2265+
# This test also handles deleting a pre-existing matching vendor
2266+
# string in advance.
2267+
dupe_entry = """
2268+
BootCurrent: 0001
2269+
Timeout: 0 seconds
2270+
BootOrder: 0000,00001
2271+
Boot0000: Vendor String HD(1, GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI)
2272+
Boot0001: Vendor String HD(2, GPT,4f3c6294-bf9b-4208-9808-be45dfc34b5c)File(\EFI\Boot\BOOTX64.EFI)
2273+
Boot0002: VENDMAGIC FvFile(9f3c6294-bf9b-4208-9808-be45dfc34b51)
2274+
""" # noqa This is a giant literal string for testing.
22622275

22632276
mock_execute.side_effect = iter([('', ''), ('', ''),
2277+
('', ''), (dupe_entry, ''),
22642278
('', ''), ('', ''),
22652279
('', ''), ('', ''),
22662280
('', '')])
@@ -2271,6 +2285,8 @@ def test__manage_uefi_found_csv(self, mkdir_mock, mock_utils_efi_part,
22712285
mock.call('mount', self.fake_efi_system_part,
22722286
self.fake_dir + '/boot/efi'),
22732287
mock.call('efibootmgr', '-v'),
2288+
mock.call('efibootmgr', '-b', '0000', '-B'),
2289+
mock.call('efibootmgr', '-b', '0001', '-B'),
22742290
mock.call('efibootmgr', '-v', '-c', '-d', self.fake_dev,
22752291
'-p', '1', '-w',
22762292
'-L', 'Vendor String', '-l',
@@ -2285,7 +2301,7 @@ def test__manage_uefi_found_csv(self, mkdir_mock, mock_utils_efi_part,
22852301
mkdir_mock.assert_called_once_with(self.fake_dir + '/boot/efi')
22862302
mock_efi_bl.assert_called_once_with(self.fake_dir + '/boot/efi')
22872303
mock_execute.assert_has_calls(expected)
2288-
self.assertEqual(7, mock_execute.call_count)
2304+
self.assertEqual(9, mock_execute.call_count)
22892305

22902306
@mock.patch.object(os.path, 'exists', lambda *_: False)
22912307
@mock.patch.object(image, '_get_efi_bootloaders', autospec=True)
@@ -2453,6 +2469,7 @@ def test__run_efibootmgr_no_bootloaders(self, mock_execute, mock_dispatch):
24532469
mock_execute.assert_has_calls(expected)
24542470

24552471
def test__run_efibootmgr(self, mock_execute, mock_dispatch):
2472+
mock_execute.return_value = ('', '')
24562473
result = image._run_efibootmgr(['EFI/BOOT/BOOTX64.EFI'],
24572474
self.fake_dev,
24582475
self.fake_efi_system_part,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Fixes cases where duplicates may not be found in the UEFI
5+
firmware NVRAM boot entry table by explicitly looking for, and deleting
6+
for matching labels in advance of creating the EFI boot loader entry.

0 commit comments

Comments
 (0)