From 5ee2eda83e06dbe8880b6f744150046731fedfa9 Mon Sep 17 00:00:00 2001 From: yaofei zheng Date: Mon, 5 Feb 2024 04:44:23 +0800 Subject: [PATCH] add efibootmgr parser (#528) * More nsd fixes (#523) * Update nsd_control.py support catz output * Update nsd_control.py fix parsing of catz zones * add testdata add corresponding catz testdata * Revert "More nsd fixes" (#526) * add efibootmgr parser * add efibootmgr completions * Multiple fixes on jc/parsers/efibootmgr.py 1. Update schema in comments. 1. Add an example. 1. Use clean and clear sub string finding sytax. 1. Wrap schema in an object. 1. Make variable scope as small as possible. * add tests for efibootmgr * Update jc/parsers/efibootmgr.py Update example using `--pretty` for output. Co-authored-by: Muescha <184316+muescha@users.noreply.github.com> * jc/parsers/efibootmgr multiple changes 1. convert all keys to "snake_case" 2. add "mirrored_percentage_above_4g" and "mirror_memory_below_4gb" 3. move conversion (from string to other types) to "_process" --------- Co-authored-by: pettai Co-authored-by: Kelly Brazil Co-authored-by: Muescha <184316+muescha@users.noreply.github.com> --- completions/jc_zsh_completion.sh | 2 + jc/lib.py | 1 + jc/parsers/efibootmgr.py | 160 ++++++++++++++++++ tests/fixtures/create_fixtures.sh | 1 + .../opensuse-leap-15.5/efibootmgr.json | 96 +++++++++++ .../opensuse-leap-15.5/efibootmgr.out | 17 ++ tests/test_efibootmgr.py | 33 ++++ 7 files changed, 310 insertions(+) create mode 100644 jc/parsers/efibootmgr.py create mode 100644 tests/fixtures/opensuse-leap-15.5/efibootmgr.json create mode 100644 tests/fixtures/opensuse-leap-15.5/efibootmgr.out create mode 100644 tests/test_efibootmgr.py diff --git a/completions/jc_zsh_completion.sh b/completions/jc_zsh_completion.sh index a458aedc..66d0fce0 100644 --- a/completions/jc_zsh_completion.sh +++ b/completions/jc_zsh_completion.sh @@ -29,6 +29,7 @@ _jc() { 'dmidecode:run "dmidecode" command with magic syntax.' 'dpkg:run "dpkg" command with magic syntax.' 'du:run "du" command with magic syntax.' + 'efibootmgr:run "efibootmgr" command with magic syntax.' 'env:run "env" command with magic syntax.' 'file:run "file" command with magic syntax.' 'findmnt:run "findmnt" command with magic syntax.' @@ -148,6 +149,7 @@ _jc() { '--dmidecode:`dmidecode` command parser' '--dpkg-l:`dpkg -l` command parser' '--du:`du` command parser' + '--efibootmgr:`efibootmgr` command parser' '--email-address:Email Address string parser' '--env:`env` command parser' '--file:`file` command parser' diff --git a/jc/lib.py b/jc/lib.py index a584a634..e1bdcf7a 100644 --- a/jc/lib.py +++ b/jc/lib.py @@ -43,6 +43,7 @@ parsers: List[str] = [ 'dmidecode', 'dpkg-l', 'du', + 'efibootmgr', 'email-address', 'env', 'file', diff --git a/jc/parsers/efibootmgr.py b/jc/parsers/efibootmgr.py new file mode 100644 index 00000000..4f967b35 --- /dev/null +++ b/jc/parsers/efibootmgr.py @@ -0,0 +1,160 @@ +"""jc - JSON Convert `efibootmgr` command output parser + +Only `efibootmgr -v` option is supported with the default output. + +Usage (cli): + + $ sudo efibootmgr | jc --efibootmgr + $ sudo efibootmgr -v | jc --efibootmgr + +or + + $ sudo jc efibootmgr + +Usage (module): + + import jc + result = jc.parse('efibootmgr', efibootmgr_command_output) + +Schema: + +[ + { + "boot_current": string, + "timeout_seconds": number, + "boot_order": [ + string, + string, + string + ], + mirrored_percentage_above_4g: number, + mirror_memory_below_4gb: bool, + "boot_options": [ + { + "boot_option_reference": string, + "display_name": string, + "uefi_device_path": string, + "boot_option_enabled": bool + }, + { + "boot_option_reference": string, + "display_name": string, + "uefi_device_path": string, + "boot_option_enabled": bool + }, + { + "boot_option_reference": string, + "display_name": string, + "uefi_device_path": string, + "boot_option_enabled": bool + } + ] + } +] + +Examples: + +$ sudo efibootmgr -v | jc --efibootmgr --pretty +[ + { + "boot_current": "0002", + "timeout_seconds": 0, + "boot_order": [ + "0002", + "0000", + "0001" + ], + "mirrored_percentage_above_4g": 0.0, + "mirror_memory_below_4gb": false, + "boot_options": [ + { + "boot_option_reference": "Boot0000", + "display_name": "WARNADO", + "uefi_device_path": "HD(1,GPT,05b9944c-1c60-492b-a510-7bbedccdc602,0x800,0xfa000)/File(EFI\\boot\\bootx64.efi)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0001", + "display_name": "Embedded NIC 1 Port 1 Partition 1", + "uefi_device_path": "VenHw(3a191845-5f86-4e78-8fce-c4cff59f9daa)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0002", + "display_name": "opensuse-secureboot", + "uefi_device_path": "HD(1,GPT,c5d4f69d-6fc2-48c7-acee-af3f30336dc5,0x800,0x19000)/File(\\EFI\\opensuse\\shim.efi)", + "boot_option_enabled": true + } + ] + } +] + +""" + +import jc.utils +import jc.parsers.universal + +class info(): + """Provides parser metadata (version, author, etc.)""" + version = '0.1' + description = '`efibootmgr -v` command parser' + author = 'Yaofei Zheng' + author_email = ['zyf26256@gmail.com','Yaofei.Zheng@dell.com'] + compatible = ['linux'] + magic_commands = ['efibootmgr'] + tags = ['command'] + +__version__ = info.version + +def _process(proc_data): + if 0 == len(proc_data): + return proc_data + proc_data[0]["timeout_seconds"] = int(proc_data[0]["timeout_seconds"].replace("seconds","").strip(), 10) + proc_data[0]["boot_order"] = proc_data[0]["boot_order"].split(',') + for boot_opt in proc_data[0]["boot_options"]: + boot_opt["boot_option_enabled"] = boot_opt["boot_option_enabled"] == '*' + proc_data[0]["mirrored_percentage_above_4g"] = float(proc_data[0]["mirrored_percentage_above_4g"]) + proc_data[0]["mirror_memory_below_4gb"] = not ("false" == proc_data[0]["mirror_memory_below_4gb"]) + return proc_data + +def parse(data, raw=False, quiet=False): + jc.utils.compatibility(__name__, info.compatible, quiet) + jc.utils.input_type_check(data) + + raw_output = [] + + if jc.utils.has_data(data): + boot_opt_list = [] + efibootmgr_dict = {} + for line in filter(None, data.splitlines()): + if "BootCurrent" in line: + efibootmgr_dict["boot_current"] = line.split(':')[1].strip() + elif "Timeout" in line: + efibootmgr_dict["timeout_seconds"] = line.split(':')[1].strip() + elif "BootOrder" in line: + efibootmgr_dict["boot_order"] = line.split(':')[1].strip() + elif "Boot" in line: + tmp_dict = {} + boot_record = line.split("\t") + tmp_dict["boot_option_reference"] = boot_record[0][0:8] + tmp_dict["display_name"] = boot_record[0][10:].strip() + if len(boot_record) > 1: + tmp_dict["uefi_device_path"] = boot_record[1].strip() + tmp_dict["boot_option_enabled"] = boot_record[0][8] + boot_opt_list.append(tmp_dict) + elif "MirroredPercentageAbove4G" in line: + efibootmgr_dict["mirrored_percentage_above_4g"] = line.split(':')[1].strip() + elif "MirrorMemoryBelow4GB" in line: + efibootmgr_dict["mirror_memory_below_4gb"] = line.split(':')[1].strip() + else: + # print(line) + continue + + efibootmgr_dict["boot_options"] = boot_opt_list + + raw_output.append(efibootmgr_dict) + + if raw: + return raw_output + else: + return _process(raw_output) diff --git a/tests/fixtures/create_fixtures.sh b/tests/fixtures/create_fixtures.sh index ffca60b1..e2980538 100644 --- a/tests/fixtures/create_fixtures.sh +++ b/tests/fixtures/create_fixtures.sh @@ -8,6 +8,7 @@ df -h > df-h.out dig www.google.com AAAA > dig-aaaa.out dig www.cnn.com www.google.com > dig.out dig -x 1.1.1.1 > dig-x.out +sudo efibootmgr -v > efibootmgr.out env > env.out free > free.out free -h > free-h.out diff --git a/tests/fixtures/opensuse-leap-15.5/efibootmgr.json b/tests/fixtures/opensuse-leap-15.5/efibootmgr.json new file mode 100644 index 00000000..b41d783f --- /dev/null +++ b/tests/fixtures/opensuse-leap-15.5/efibootmgr.json @@ -0,0 +1,96 @@ +[ + { + "boot_current": "0004", + "timeout_seconds": 0, + "boot_order": [ + "0004", + "0001", + "0007", + "0002", + "0003", + "0009", + "0000", + "0005", + "0006", + "0008", + "000A", + "000B" + ], + "mirrored_percentage_above_4g": 0.0, + "mirror_memory_below_4gb": false, + "boot_options": [ + { + "boot_option_reference": "Boot0000", + "display_name": "debian", + "uefi_device_path": "HD(1,GPT,66b2a325-3129-45d3-84dd-c1a4e4bd846b,0x800,0x100000)/File(\\EFI\\debian\\shimx64.efi)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0001", + "display_name": "RecoveryBoot", + "uefi_device_path": "HD(3,GPT,a75ab570-de77-504c-be97-35faa629f1f3,0x5dc800,0x1f4000)/File(EFI\\boot\\bootx64.efi)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0002", + "display_name": "Embedded NIC 1 Port 1 Partition 1", + "uefi_device_path": "VenHw(3a191845-5f86-4e78-8fce-c4cff59f9daa)", + "boot_option_enabled": false + }, + { + "boot_option_reference": "Boot0003", + "display_name": "Embedded NIC 2 Port 1 Partition 1", + "uefi_device_path": "VenHw(d227c733-f75f-4341-b749-4d1759ec8538)", + "boot_option_enabled": false + }, + { + "boot_option_reference": "Boot0004", + "display_name": "PrimaryBoot", + "uefi_device_path": "HD(1,GPT,faf946be-a174-478f-82ea-d90aa476fb91,0x800,0xfa000)/File(EFI\\boot\\bootx64.efi)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0005", + "display_name": "EFI Fixed Disk Boot Device 3", + "uefi_device_path": "PciRoot(0x1)/Pci(0x4,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D4-0F-01)/HD(1,GPT,c2336d30-9bde-4a0b-b9be-145d6958d5af,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0006", + "display_name": "EFI Fixed Disk Boot Device 4", + "uefi_device_path": "PciRoot(0x11)/Pci(0x1,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B8-C1)/HD(1,GPT,1fbfeaec-d78d-4d8c-93ae-77bba6c09d3f,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0007", + "display_name": "BackendBoot", + "uefi_device_path": "PciRoot(0x11)/Pci(0x3,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B5-B6)/HD(1,GPT,b95c4d58-849f-40fa-bf2b-4f0b8a0aa8bd,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0008", + "display_name": "EFI Fixed Disk Boot Device 6", + "uefi_device_path": "PciRoot(0x11)/Pci(0x6,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D3-90-01)/HD(1,GPT,f543ab7b-9d55-4e57-8afe-bdb907428f15,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot0009", + "display_name": "EFI Fixed Disk Boot Device 7", + "uefi_device_path": "PciRoot(0x12)/Pci(0x6,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B6-08)/HD(1,GPT,db9522dc-062b-4efa-84b5-6956cea7b5a6,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot000A", + "display_name": "EFI Fixed Disk Boot Device 8", + "uefi_device_path": "PciRoot(0x12)/Pci(0x8,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B6-0D)/HD(1,GPT,4a9e1d40-b853-44a0-8d26-a4df6f112dc9,0x1000,0x80001)", + "boot_option_enabled": true + }, + { + "boot_option_reference": "Boot000B", + "display_name": "EFI Fixed Disk Boot Device 9", + "uefi_device_path": "PciRoot(0x13)/Pci(0x5,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D3-8E-01)/HD(1,GPT,5a80e168-bac7-4967-9507-e23a432bae18,0x1000,0x80001)", + "boot_option_enabled": true + } + ] + } +] diff --git a/tests/fixtures/opensuse-leap-15.5/efibootmgr.out b/tests/fixtures/opensuse-leap-15.5/efibootmgr.out new file mode 100644 index 00000000..625849c6 --- /dev/null +++ b/tests/fixtures/opensuse-leap-15.5/efibootmgr.out @@ -0,0 +1,17 @@ +BootCurrent: 0004 +Timeout: 0 seconds +BootOrder: 0004,0001,0007,0002,0003,0009,0000,0005,0006,0008,000A,000B +Boot0000* debian HD(1,GPT,66b2a325-3129-45d3-84dd-c1a4e4bd846b,0x800,0x100000)/File(\EFI\debian\shimx64.efi) +Boot0001* RecoveryBoot HD(3,GPT,a75ab570-de77-504c-be97-35faa629f1f3,0x5dc800,0x1f4000)/File(EFI\boot\bootx64.efi) +Boot0002 Embedded NIC 1 Port 1 Partition 1 VenHw(3a191845-5f86-4e78-8fce-c4cff59f9daa) +Boot0003 Embedded NIC 2 Port 1 Partition 1 VenHw(d227c733-f75f-4341-b749-4d1759ec8538) +Boot0004* PrimaryBoot HD(1,GPT,faf946be-a174-478f-82ea-d90aa476fb91,0x800,0xfa000)/File(EFI\boot\bootx64.efi) +Boot0005* EFI Fixed Disk Boot Device 3 PciRoot(0x1)/Pci(0x4,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D4-0F-01)/HD(1,GPT,c2336d30-9bde-4a0b-b9be-145d6958d5af,0x1000,0x80001) +Boot0006* EFI Fixed Disk Boot Device 4 PciRoot(0x11)/Pci(0x1,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B8-C1)/HD(1,GPT,1fbfeaec-d78d-4d8c-93ae-77bba6c09d3f,0x1000,0x80001) +Boot0007* BackendBoot PciRoot(0x11)/Pci(0x3,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B5-B6)/HD(1,GPT,b95c4d58-849f-40fa-bf2b-4f0b8a0aa8bd,0x1000,0x80001) +Boot0008* EFI Fixed Disk Boot Device 6 PciRoot(0x11)/Pci(0x6,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D3-90-01)/HD(1,GPT,f543ab7b-9d55-4e57-8afe-bdb907428f15,0x1000,0x80001) +Boot0009* EFI Fixed Disk Boot Device 7 PciRoot(0x12)/Pci(0x6,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B6-08)/HD(1,GPT,db9522dc-062b-4efa-84b5-6956cea7b5a6,0x1000,0x80001) +Boot000A* EFI Fixed Disk Boot Device 8 PciRoot(0x12)/Pci(0x8,0x0)/Pci(0x0,0x0)/NVMe(0x1,00-25-38-95-21-01-B6-0D)/HD(1,GPT,4a9e1d40-b853-44a0-8d26-a4df6f112dc9,0x1000,0x80001) +Boot000B* EFI Fixed Disk Boot Device 9 PciRoot(0x13)/Pci(0x5,0x0)/Pci(0x0,0x0)/NVMe(0x1,8C-E3-8E-E2-0A-D3-8E-01)/HD(1,GPT,5a80e168-bac7-4967-9507-e23a432bae18,0x1000,0x80001) +MirroredPercentageAbove4G: 0.00 +MirrorMemoryBelow4GB: false diff --git a/tests/test_efibootmgr.py b/tests/test_efibootmgr.py new file mode 100644 index 00000000..2ef7c846 --- /dev/null +++ b/tests/test_efibootmgr.py @@ -0,0 +1,33 @@ +import os +import json +import unittest +import jc.parsers.efibootmgr + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + + +class MyTests(unittest.TestCase): + + # input + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/opensuse-leap-15.5/efibootmgr.out'), 'r', encoding='utf-8') as f: + opensuse_leap_15_5_efibootmgr = f.read() + + # output + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/opensuse-leap-15.5/efibootmgr.json'), 'r', encoding='utf-8') as f: + opensuse_leap_15_5_efibootmgr_json = json.loads(f.read()) + + def test_efibootmgr_nodata(self): + """ + Test 'efibootmgr' with no data + """ + self.assertEqual(jc.parsers.efibootmgr.parse('', quiet=True), []) + + def test_efibootmgr_opensuse_leap_15_5(self): + """ + Test 'efibootmgr' on Opensuse Leap 15.5 + """ + self.assertEqual(jc.parsers.efibootmgr.parse(self.opensuse_leap_15_5_efibootmgr, quiet=True), self.opensuse_leap_15_5_efibootmgr_json) + + +if __name__ == '__main__': + unittest.main()