diff --git a/tests/data/grub-linux-no-kernel.cfg b/tests/data/grub-linux-no-kernel.cfg new file mode 100644 index 00000000..6f0a6df2 --- /dev/null +++ b/tests/data/grub-linux-no-kernel.cfg @@ -0,0 +1,3 @@ +menuentry 'Linux - Safe Mode' { + initrd /boot/initrd.img-1 +} diff --git a/tests/data/grub-linux.cfg b/tests/data/grub-linux.cfg index ae7a104f..bb0372b9 100644 --- a/tests/data/grub-linux.cfg +++ b/tests/data/grub-linux.cfg @@ -3,10 +3,3 @@ menuentry 'Linux - Safe Mode' { linux /boot/vmlinuz-1 ro initrd /boot/initrd.img-1 } -menuentry 'XCP-ng (Trusted Boot)' { - search --label --set root root-vgdorj - multiboot2 /boot/tboot.gz logging=serial,memory - module2 /boot/xen.gz dom0_mem=7584M,max:7584M watchdog crashkernel=256M,below=4G - module2 /boot/vmlinuz-6.1-xen root=LABEL=root-vgdorj ro console=hvc0 console=tty0 - module2 /boot/initrd-6.1-xen.img -} \ No newline at end of file diff --git a/tests/data/grub-no-hypervisor.cfg b/tests/data/grub-no-hypervisor.cfg new file mode 100644 index 00000000..348e8457 --- /dev/null +++ b/tests/data/grub-no-hypervisor.cfg @@ -0,0 +1,10 @@ +serial --unit=0 --speed=115200 +terminal_input serial console +terminal_output serial console +set default=0 +set timeout=5 +menuentry 'XCP-ng' { + search --label --set root root-vgdorj + xen_module /boot/vmlinuz-4.19-xen root=LABEL=root-vgdorj ro nolvm hpet=disable console=hvc0 console=tty0 quiet vga=785 splash plymouth.ignore-serial-consoles + xen_module /boot/initrd-4.19-xen.img +} diff --git a/tests/data/grub-no-multiboot.cfg b/tests/data/grub-no-multiboot.cfg new file mode 100644 index 00000000..afac3d18 --- /dev/null +++ b/tests/data/grub-no-multiboot.cfg @@ -0,0 +1,10 @@ +serial --unit=0 --speed=115200 +terminal_input serial console +terminal_output serial console +set default=0 +set timeout=5 +menuentry 'XCP-ng' { + search --label --set root root-vgdorj + module2 /boot/vmlinuz-4.19-xen root=LABEL=root-vgdorj ro nolvm hpet=disable console=hvc0 console=tty0 quiet vga=785 splash plymouth.ignore-serial-consoles + module2 /boot/initrd-4.19-xen.img +} diff --git a/tests/data/grub-xen-boot.cfg b/tests/data/grub-xen-boot.cfg new file mode 100644 index 00000000..88ce234f --- /dev/null +++ b/tests/data/grub-xen-boot.cfg @@ -0,0 +1,35 @@ +serial --unit=0 --speed=115200 +terminal_input serial console +terminal_output serial console +set default=0 +set timeout=5 +menuentry 'XCP-ng' { + search --label --set root root-vgdorj + xen_hypervisor /boot/xen.gz dom0_mem=7584M,max:7584M watchdog ucode=scan dom0_max_vcpus=1-16 crashkernel=256M,below=4G console=vga vga=mode-0x0311 + xen_module /boot/vmlinuz-4.19-xen root=LABEL=root-vgdorj ro nolvm hpet=disable console=hvc0 console=tty0 quiet vga=785 splash plymouth.ignore-serial-consoles + xen_module /boot/initrd-4.19-xen.img +} +menuentry 'XCP-ng (Serial)' { + search --label --set root root-vgdorj + xen_hypervisor /boot/xen.gz com1=115200,8n1 console=com1,vga dom0_mem=7584M,max:7584M watchdog ucode=scan dom0_max_vcpus=1-16 crashkernel=256M,below=4G + xen_module /boot/vmlinuz-4.19-xen root=LABEL=root-vgdorj ro nolvm hpet=disable console=tty0 console=hvc0 + xen_module /boot/initrd-4.19-xen.img +} +menuentry 'XCP-ng in Safe Mode' { + search --label --set root root-vgdorj + xen_hypervisor /boot/xen.gz nosmp noreboot noirqbalance no-mce no-bootscrub no-numa no-hap no-mmcfg max_cstate=0 nmi=ignore allow_unsafe dom0_mem=7584M,max:7584M com1=115200,8n1 console=com1,vga + xen_module /boot/vmlinuz-4.19-xen earlyprintk=xen root=LABEL=root-vgdorj ro nolvm hpet=disable console=tty0 console=hvc0 + xen_module /boot/initrd-4.19-xen.img +} +menuentry 'XCP-ng (Xen 4.13.1 / Linux 4.19.0+1)' { + search --label --set root root-vgdorj + xen_hypervisor /boot/xen-fallback.gz dom0_mem=7584M,max:7584M watchdog ucode=scan dom0_max_vcpus=1-16 crashkernel=256M,below=4G + xen_module /boot/vmlinuz-fallback root=LABEL=root-vgdorj ro nolvm hpet=disable console=hvc0 console=tty0 + xen_module /boot/initrd-fallback.img +} +menuentry 'XCP-ng (Serial, Xen 4.13.1 / Linux 4.19.0+1)' { + search --label --set root root-vgdorj + xen_hypervisor /boot/xen-fallback.gz com1=115200,8n1 console=com1,vga dom0_mem=7584M,max:7584M watchdog ucode=scan dom0_max_vcpus=1-16 crashkernel=256M,below=4G + xen_module /boot/vmlinuz-fallback root=LABEL=root-vgdorj ro nolvm hpet=disable console=tty0 console=hvc0 + xen_module /boot/initrd-fallback.img +} diff --git a/tests/test_bootloader.py b/tests/test_bootloader.py index 82500336..7a0a7a0a 100644 --- a/tests/test_bootloader.py +++ b/tests/test_bootloader.py @@ -4,48 +4,137 @@ import subprocess from tempfile import NamedTemporaryFile, mkdtemp -from xcp.bootloader import Bootloader +from xcp.bootloader import Bootloader, Grub2Format, MenuEntry from xcp.compat import open_with_codec_handling -def test_writeGrubWithTempFile(tmpdir): - """Test xcp.bootloader.{writeGrub,writeExtLinux}.open_with_codec_handling with tmpdir fixture""" - bootloader = Bootloader.readGrub2("tests/data/grub.cfg") - filename = str(tmpdir.mkdir("grub").join("menu.lst")) - bootloader.writeGrub(filename) - Bootloader.readGrub(filename) - bootloader.writeExtLinux(filename) - Bootloader.readExtLinux(filename) - - -def test_GrubLegacyExtLinuxWithTempFile(tmpdir): - """Note: GRUB-Legacy and EXTLINUX code can likely be removed (pending approval)""" - bootloader = Bootloader.readGrub2("tests/data/grub-linux.cfg") - filename = str(tmpdir.mkdir("grub").join("menu.lst")) - bootloader.writeExtLinux(filename) - Bootloader.readExtLinux(filename) - bootloader.menu.pop("xe-tboot") - bootloader.menu_order.remove("xe-tboot") - bootloader.writeGrub(filename) - Bootloader.readGrub(filename) - - class TestBootloader(unittest.TestCase): - def test_grub2(self): - bl = Bootloader.readGrub2("tests/data/grub.cfg") + def _test_cfg(self, cfg): + bl = Bootloader.readGrub2(cfg) with NamedTemporaryFile("w") as temp: bl.writeGrub2(temp.name) # get a diff - proc = subprocess.Popen(["diff", "tests/data/grub.cfg", temp.name], + proc = subprocess.Popen(["diff", cfg, temp.name], stdout = subprocess.PIPE, universal_newlines=True) - assert proc.stdout - for line in proc.stdout: - # pylint: disable-next=deprecated-method - self.assertRegexpMatches(line, r"^(5a6,13$|>)") - proc.stdout.close() - proc.wait() + self.assertEqual(proc.stdout.read(), '''5a6,13 +> if [ -s $prefix/grubenv ]; then +> load_env +> fi +> +> if [ -n "$override_entry" ]; then +> set default=$override_entry +> fi +> +''') + proc.stdout.close() + proc.wait() + self.assertEqual(proc.returncode, 1) + + def test_grub2(self): + '''Test read/write roundtrip of GRUB2 multiboot config''' + self._test_cfg("tests/data/grub.cfg") + + def test_grub2_xen_boot(self): + '''Test read/write roundtrip of GRUB2 xen_boot config''' + self._test_cfg("tests/data/grub-xen-boot.cfg") + + def test_no_multiboot(self): + # A module2 line without a multiboot2 line is an error + with self.assertRaises(RuntimeError): + Bootloader.readGrub2("tests/data/grub-no-multiboot.cfg") + + def test_no_hypervisor(self): + # A xen_module line without a xen_hypervisor line is an error + with self.assertRaises(RuntimeError): + Bootloader.readGrub2("tests/data/grub-no-hypervisor.cfg") + + +class TestMenuEntry(unittest.TestCase): + def setUp(self): + self.tmpdir = mkdtemp(prefix="testbl") + self.fn = os.path.join(self.tmpdir, 'grub.cfg') + self.bl = Bootloader('grub2', self.fn) + + def tearDown(self): + shutil.rmtree(self.tmpdir) + + def test_new_multiboot(self): + # No format specified, default to multiboot2 + e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2', + kernel='vmlinuz', kernel_args='karg1 karg2', + initrd='initrd.img', title='xe') + self.bl.append('xe', e) + + e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2', + kernel='vmlinuz', kernel_args='karg1 karg2', + initrd='initrd.img', title='xe-serial') + e.entry_format = Grub2Format.MULTIBOOT2 + self.bl.append('xe-serial', e) + + self.bl.commit() + + with open_with_codec_handling(self.fn, 'r') as f: + content = f.read() + + self.assertEqual(content, '''menuentry 'xe' { + multiboot2 xen.efi xarg1 xarg2 + module2 vmlinuz karg1 karg2 + module2 initrd.img +} +menuentry 'xe-serial' { + multiboot2 xen.efi xarg1 xarg2 + module2 vmlinuz karg1 karg2 + module2 initrd.img +} +''') + + def test_new_xen_boot(self): + e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2', + kernel='vmlinuz', kernel_args='karg1 karg2', + initrd='initrd.img', title='xe') + e.entry_format = Grub2Format.XEN_BOOT + self.bl.append('xe', e) + self.bl.commit() + + with open_with_codec_handling(self.fn, 'r') as f: + content = f.read() + + self.assertEqual(content, '''menuentry 'xe' { + xen_hypervisor xen.efi xarg1 xarg2 + xen_module vmlinuz karg1 karg2 + xen_module initrd.img +} +''') + + def test_new_linux(self): + e = MenuEntry(hypervisor='', hypervisor_args='', + kernel='vmlinuz', kernel_args='karg1 karg2', + initrd='initrd.img', title='linux') + self.bl.append('linux', e) + self.bl.commit() + + e = MenuEntry(hypervisor='', hypervisor_args='', + kernel='vmlinuz2', kernel_args='karg3 karg4', + initrd='initrd2.img', title='linux2') + e.entry_format = Grub2Format.LINUX + self.bl.append('linux2', e) + self.bl.commit() + + with open_with_codec_handling(self.fn, 'r') as f: + content = f.read() + + self.assertEqual(content, '''menuentry 'linux' { + linux vmlinuz karg1 karg2 + initrd initrd.img +} +menuentry 'linux2' { + linux vmlinuz2 karg3 karg4 + initrd initrd2.img +} +''') + class TestLinuxBootloader(unittest.TestCase): def setUp(self): @@ -80,7 +169,6 @@ def test_grub2_newdefault(self): "", ], [], - [], ] assert str(bl.default).startswith("safe") assert bl.location == "mbr" @@ -90,34 +178,27 @@ def test_grub2_newdefault(self): assert bl.menu["safe"].kernel == "/boot/vmlinuz-2" assert bl.menu["safe"].kernel_args == "ro" assert bl.menu["safe"].initrd == "/boot/initrd.img-2" - assert bl.menu["xe-tboot"].title == "XCP-ng (Trusted Boot)" - assert bl.menu["xe-tboot"].hypervisor == "/boot/xen.gz" - assert bl.menu["xe-tboot"].tboot == "/boot/tboot.gz" - assert bl.menu["xe-tboot"].getTbootArgs() == ["logging=serial,memory"] + + def test_no_kernel(self): + # An initrd line without a kernel line is an error + with self.assertRaises(RuntimeError): + Bootloader.readGrub2("tests/data/grub-linux-no-kernel.cfg") + class TestBootloaderAdHoc(unittest.TestCase): def setUp(self): self.bl = Bootloader.readGrub2("tests/data/grub.cfg") check_config(self.bl) - def test_grub(self): + def test_grub2(self): with NamedTemporaryFile("w", delete=False) as temp: - self.bl.writeGrub(temp) - bl2 = Bootloader.readGrub(temp.name) + self.bl.writeGrub2(temp) + bl2 = Bootloader.readGrub2(temp.name) # Check config from tests/data/grub.cfg: os.unlink(temp.name) assert bl2.serial == {"port": 0, "baud": 115200} check_config(bl2) - def test_extlinux(self): - with NamedTemporaryFile("w", delete=False) as temp: - self.bl.writeExtLinux(temp) - bl2 = Bootloader.readExtLinux(temp.name) - os.unlink(temp.name) - # readExtLinux tries to read flow-control (there is none in tests/data/grub.cfg): - assert bl2.serial == {"port": 0, "baud": 115200, "flow": None} - check_config(bl2) - def check_config(bl): # Check config from tests/data/grub.cfg: diff --git a/xcp/bootloader.py b/xcp/bootloader.py index 2cadd5c4..695c3413 100644 --- a/xcp/bootloader.py +++ b/xcp/bootloader.py @@ -24,6 +24,7 @@ from __future__ import division, print_function import copy +from enum import Enum import os import os.path import re @@ -42,14 +43,17 @@ COUNTER = 0 +class Grub2Format(Enum): + MULTIBOOT2 = 0 + LINUX = 1 + XEN_BOOT = 2 + class MenuEntry(object): + # pylint: disable=too-many-positional-arguments def __init__(self, hypervisor, hypervisor_args, kernel, kernel_args, - initrd, title = None, tboot = None, tboot_args = None, - root = None): + initrd, title = None, root = None): self.extra = None self.contents = [] - self.tboot = tboot - self.tboot_args = tboot_args self.hypervisor = hypervisor self.hypervisor_args = hypervisor_args self.kernel = kernel @@ -57,12 +61,7 @@ def __init__(self, hypervisor, hypervisor_args, kernel, kernel_args, self.initrd = initrd self.title = title self.root = root - - def getTbootArgs(self): - return re.findall(r'\S[^ "]*(?:"[^"]*")?\S*', cast(str, self.tboot_args)) - - def setTbootArgs(self, args): - self.tboot_args = ' '.join(args) + self.entry_format = None def getHypervisorArgs(self): return re.findall(r'\S[^ "]*(?:"[^"]*")?\S*', self.hypervisor_args) @@ -77,6 +76,7 @@ def setKernelArgs(self, args): self.kernel_args = ' '.join(args) class Bootloader(object): + # pylint: disable=too-many-positional-arguments def __init__(self, src_fmt, src_file, menu = None, menu_order = None, default = None, timeout = None, serial = None, location = None, env_block = None): @@ -106,200 +106,6 @@ def remove(self, label): del self.menu[label] self.menu_order.remove(label) - @classmethod - def readExtLinux(cls, src_file): - # type:(str) -> Bootloader - menu = {} # type: dict[str, MenuEntry | dict[str, str]] - menu_order = [] - default = None - timeout = None - location = None - serial = None - label = None - title = None - kernel = None - - fh = open_textfile(src_file, "r") - try: - for line in fh: - l = line.strip() - els = l.split(None, 2) - if len(els) == 0: - continue - keywrd = els[0].lower() - - # header - if l.startswith('# location ') and len(els) == 3 and els[2] in ['mbr', 'partition']: - location = els[2] - elif keywrd == 'serial' and len(els) > 1: - baud = '9600' - flow = None - if len(els) > 2: - if ' ' in els[2]: - baud, flow = els[2].split(None, 1) - else: - baud = els[2] - serial = {'port': int(els[1]), 'baud': int(baud), 'flow': flow} - elif keywrd == 'default' and len(els) == 2: - default = els[1] - elif keywrd == 'timeout' and len(els) == 2: - timeout = int(els[1]) - - # menu - elif keywrd == 'label' and len(els) == 2: - label = els[1] - menu[label] = {} - menu_order.append(label) - title = None - elif label: - if keywrd == '#': - title = l[1:].lstrip() - elif keywrd == 'kernel' and len(els) > 1: - kernel = els[1] - elif keywrd == 'append' and len(els) > 1 and kernel == 'mboot.c32': - if 'tboot' in els[1]: - # els[2] contains tboot args, hypervisor, - # hypervisor args, kernel, - # kernel args & initrd - args = [x.strip() for x in els[2].split('---')] - if len(args) == 4: - hypervisor = args[1].split(None, 1) - kernel = args[2].split(None, 1) # type:ignore[assignment] # mypy - if len(hypervisor) == 2 and len(kernel) == 2: - menu[label] = MenuEntry(tboot = els[1], - tboot_args = args[0], - hypervisor = hypervisor[0], - hypervisor_args = hypervisor[1], - kernel = kernel[0], - kernel_args = kernel[1], - initrd = args[3], - title = title) - elif 'xen' in els[1]: - # els[2] contains hypervisor args, kernel, - # kernel args & initrd - args = [x.strip() for x in els[2].split('---')] - if len(args) == 3: - kernel = args[1].split(None, 1) # type:ignore[assignment] # mypy - if len(kernel) == 2: - menu[label] = MenuEntry(hypervisor = els[1], - hypervisor_args = args[0], - kernel = kernel[0], - kernel_args = kernel[1], - initrd = args[2], - title = title) - finally: - fh.close() - - return cls('extlinux', src_file, menu, menu_order, default, timeout, - serial, location) - - @classmethod - def readGrub(cls, src_file): - # type: (str) -> Bootloader - menu = {} - menu_order = [] - default = 0 - timeout = None - location = None - serial = None - label = None - title = None - hypervisor = None - hypervisor_args = None - kernel = None - kernel_args = None - - def create_label(title): - global COUNTER - - if title == branding.PRODUCT_BRAND: - return 'xe' - - if title.endswith('(Serial)'): - return 'xe-serial' - if title.endswith('Safe Mode'): - return 'safe' - if ' / ' in title: - if '(Serial,' in title: - return 'fallback-serial' - else: - return 'fallback' - COUNTER += 1 - return "label%d" % COUNTER - - fh = open_textfile(src_file, "r") - try: - for line in fh: - l = line.strip() - els = l.split(None, 2) - if len(els) == 0: - continue - - # header - if l.startswith('# location ') and len(els) == 3 and els[2] in ['mbr', 'partition']: - location = els[2] - elif els[0] == 'serial' and len(els) > 1: - port = 0 - baud = 9600 - for arg in l.split(None, 1)[1].split(): - if '=' in arg: - opt, val = arg.split('=') - if opt == '--unit': - port = int(val) - elif opt == '--speed': - baud = int(val) - serial = {'port': port, 'baud': baud} - elif els[0] == 'default' and len(els) == 2: - # default is index into menu list, fixup later - default = int(els[1]) - elif els[0] == 'timeout' and len(els) == 2: - timeout = int(els[1]) * 10 - - # menu - elif els[0] == 'title' and len(els) > 1: - title = l.split(None, 1)[1] - elif title: - if els[0] == 'kernel' and len(els) > 2: - hypervisor, hypervisor_args = (l.split(None, 1) - [1].split(None, 1)) - elif els[0] == 'module' and len(els) > 1: - if kernel and hypervisor: - # second module == initrd - label = create_label(title) - menu_order.append(label) - menu[label] = MenuEntry(hypervisor = hypervisor, - hypervisor_args = hypervisor_args, - kernel = kernel, - kernel_args = kernel_args, - initrd = els[1], title = title) - hypervisor = None - kernel = None - else: - kernel, kernel_args = (l.split(None, 1) - [1].split(None, 1)) - elif els[0] == 'initrd' and len(els) > 1: - # not multiboot - kernel = hypervisor - kernel_args = hypervisor_args - label = create_label(title) - menu_order.append(label) - menu[label] = MenuEntry(None, - None, - kernel = kernel, - kernel_args = kernel_args, - initrd = els[1], title = title) - hypervisor = None - hypervisor_args = None - - # fixup default - if len(menu_order) > default: - default = menu_order[default] - finally: - fh.close() - - return cls('grub', src_file, menu, menu_order, default, - timeout, serial, location) - @classmethod def readGrub2(cls, src_file): # type:(str) -> Bootloader @@ -309,8 +115,6 @@ def readGrub2(cls, src_file): timeout = None serial = None title = None - tboot = None - tboot_args = None hypervisor = None hypervisor_args = None kernel = None @@ -321,16 +125,13 @@ def readGrub2(cls, src_file): menu_entry_contents = [] # type: list[str] boilerplate = [] # type: list[str] boilerplates = [] # type: list[list[str]] + entry_format = Grub2Format.MULTIBOOT2 def create_label(title): global COUNTER if title == branding.PRODUCT_BRAND: return 'xe' - if title.endswith('(Serial) (Trusted Boot)'): - return 'xe-serial-tboot' - if title.endswith('(Trusted Boot)'): - return 'xe-tboot' if title.endswith('(Serial)'): return 'xe-serial' if title.endswith('Safe Mode'): @@ -350,7 +151,7 @@ def parse_boot_entry(line): entry = parts[1] if len(parts) > 1 else "" args = parts[2] if len(parts) > 2 else "" return entry, args - + fh = open_textfile(src_file, "r") try: for line in fh: @@ -371,7 +172,7 @@ def parse_boot_entry(line): if match: serial = { 'port': int(match.group(1)), - 'baud': match.group(2) + 'baud': int(match.group(2)), } elif l.startswith('terminal_'): pass @@ -400,29 +201,37 @@ def parse_boot_entry(line): boilerplate = [] elif title: if l.startswith("multiboot2"): - if "tboot" in l: - tboot, tboot_args = parse_boot_entry(l) - else: - hypervisor, hypervisor_args = parse_boot_entry(l) + hypervisor, hypervisor_args = parse_boot_entry(l) + elif l.startswith("xen_hypervisor"): + entry_format = Grub2Format.XEN_BOOT + hypervisor, hypervisor_args = parse_boot_entry(l) elif l.startswith("module2"): if not hypervisor: - hypervisor, hypervisor_args = parse_boot_entry(l) - elif kernel: + raise RuntimeError("Need a multiboot2 kernel") + if kernel: + initrd = l.split(None, 1)[1] + else: + kernel, kernel_args = parse_boot_entry(l) + elif l.startswith("xen_module"): + if not hypervisor: + raise RuntimeError("Need a hypervisor") + if kernel: initrd = l.split(None, 1)[1] else: kernel, kernel_args = parse_boot_entry(l) elif l.startswith("linux"): + entry_format = Grub2Format.LINUX kernel, kernel_args = parse_boot_entry(l) elif l.startswith("initrd"): + if not kernel: + raise RuntimeError("Need a kernel") initrd = l.split(None, 1)[1] elif l.startswith("search --label --set root"): root = l.split()[4] elif l == "}": label = create_label(title) menu_order.append(label) - menu[label] = MenuEntry(tboot = tboot, - tboot_args = tboot_args, - hypervisor = hypervisor, + menu[label] = MenuEntry(hypervisor = hypervisor, hypervisor_args = hypervisor_args, kernel = kernel, kernel_args = kernel_args, @@ -430,10 +239,9 @@ def parse_boot_entry(line): root = root) menu[label].extra = menu_entry_extra menu[label].contents = menu_entry_contents + menu[label].entry_format = entry_format title = None - tboot = None - tboot_args = None hypervisor = None hypervisor_args = None kernel = None @@ -442,6 +250,7 @@ def parse_boot_entry(line): root = None menu_entry_extra = None menu_entry_contents = [] + entry_format = Grub2Format.MULTIBOOT2 else: menu_entry_contents.append(line.rstrip()) @@ -472,89 +281,9 @@ def loadExisting(cls, root = '/'): return cls.readGrub2(os.path.join(root, "boot/grub/grub.cfg")) elif os.path.exists(os.path.join(root, "boot/grub2/grub.cfg")): return cls.readGrub2(os.path.join(root, "boot/grub2/grub.cfg")) - elif os.path.exists(os.path.join(root, "boot/extlinux.conf")): - return cls.readExtLinux(os.path.join(root, "boot/extlinux.conf")) - elif os.path.exists(os.path.join(root, "boot/grub/menu.lst")): - return cls.readGrub(os.path.join(root, "boot/grub/menu.lst")) else: raise RuntimeError("No existing bootloader configuration found") - def writeExtLinux(self, dst_file = None): - if dst_file and hasattr(dst_file, 'name'): - fh = dst_file - else: - fh = open_textfile(cast(str, dst_file), "w") - print("# location " + self.location, file=fh) - - if self.serial: - if self.serial.get("flow", None) is None: - print("serial %s %s" % (self.serial['port'], - self.serial['baud']), file=fh) - else: - print("serial %s %s %s" % (self.serial['port'], - self.serial['baud'], - self.serial['flow']), file=fh) - if self.default: - print("default " + self.default, file=fh) - print("prompt 1", file=fh) - if self.timeout: - print("timeout %d" % self.timeout, file=fh) - - for label in self.menu_order: - print("\nlabel " + label, file=fh) - m = self.menu[label] - if m.title: - print(" # " + m.title, file=fh) - if m.tboot: - print(" kernel mboot.c32", file=fh) - print(" append %s %s --- %s %s --- %s %s --- %s" % - (m.tboot, m.tboot_args, m.hypervisor, m.hypervisor_args, - m.kernel, m.kernel_args, m.initrd), file=fh) - elif m.hypervisor: - print(" kernel mboot.c32", file=fh) - print(" append %s %s --- %s %s --- %s" % - (m.hypervisor, m.hypervisor_args, m.kernel, m.kernel_args, m.initrd), file=fh) - else: - print(" kernel " + m.kernel, file=fh) - print(" append " + m.kernel_args, file=fh) - print(" initrd " + m.initrd, file=fh) - if not hasattr(dst_file, 'name'): - fh.close() - - def writeGrub(self, dst_file = None): - if dst_file and hasattr(dst_file, 'name'): - fh = dst_file - else: - fh = open_textfile(cast(str, dst_file), "w") - print("# location " + self.location, file=fh) - - if self.serial: - print("serial --unit=%s --speed=%s" % - (self.serial['port'], self.serial['baud']), file=fh) - print("terminal --timeout=10 console serial", file=fh) - else: - print("terminal console", file=fh) - if self.default: - for i in range(len(self.menu_order)): - if self.menu_order[i] == self.default: - print("default %d" % i, file=fh) - break - if self.timeout: - print("timeout %d" % (self.timeout // 10), file=fh) - - for label in self.menu_order: - m = self.menu[label] - print("\ntitle " + m.title, file=fh) - if m.hypervisor: - print(" kernel " + m.hypervisor + " " + m.hypervisor_args, file=fh) - print(" module " + m.kernel + " " + m.kernel_args, file=fh) - print(" module " + m.initrd, file=fh) - else: - print(" kernel " + m.kernel + " " + m.kernel_args, file=fh) - print(" initrd " + m.initrd, file=fh) - if not hasattr(dst_file, 'name'): - fh.close() - def writeGrub2(self, dst_file = None): if dst_file and hasattr(dst_file, 'name'): fh = dst_file @@ -600,21 +329,27 @@ def writeGrub2(self, dst_file = None): if m.root: print("\tsearch --label --set root %s" % m.root, file=fh) - if m.hypervisor: - if m.tboot: - print("\tmultiboot2 %s %s" % (m.tboot, m.tboot_args), file=fh) - print("\tmodule2 %s %s" % (m.hypervisor, m.hypervisor_args), file=fh) - else: - print("\tmultiboot2 %s %s" % (m.hypervisor, m.hypervisor_args), file=fh) + if ((m.entry_format is None and m.hypervisor) or + m.entry_format == Grub2Format.MULTIBOOT2): + print("\tmultiboot2 %s %s" % (m.hypervisor, m.hypervisor_args), file=fh) if m.kernel: print("\tmodule2 %s %s" % (m.kernel, m.kernel_args), file=fh) if m.initrd: print("\tmodule2 %s" % m.initrd, file=fh) - else: - if m.kernel: - print("\tlinux %s %s" % (m.kernel, m.kernel_args), file=fh) + elif ((m.entry_format is None and not m.hypervisor) or + m.entry_format == Grub2Format.LINUX): + print("\tlinux %s %s" % (m.kernel, m.kernel_args), file=fh) if m.initrd: print("\tinitrd %s" % m.initrd, file=fh) + elif m.entry_format == Grub2Format.XEN_BOOT: + print("\txen_hypervisor %s %s" % (m.hypervisor, m.hypervisor_args), file=fh) + if m.kernel: + print("\txen_module %s %s" % (m.kernel, m.kernel_args), file=fh) + if m.initrd: + print("\txen_module %s" % m.initrd, file=fh) + else: + raise AssertionError("Unreachable") + print("}", file=fh) if not hasattr(dst_file, 'name'): fh.close() @@ -626,12 +361,8 @@ def commit(self, dst_file = None): # write to temp file in final destination directory fd, tmp_file = tempfile.mkstemp(dir = os.path.dirname(dst_file)) - if self.src_fmt == 'extlinux': - self.writeExtLinux(tmp_file) - elif self.src_fmt == 'grub': - self.writeGrub(tmp_file) - elif self.src_fmt == 'grub2': - self.writeGrub2(tmp_file) + assert self.src_fmt == 'grub2' + self.writeGrub2(tmp_file) # atomically replace destination file os.close(fd)