diff --git a/stages/org.osbuild.grub2.inst b/stages/org.osbuild.grub2.inst new file mode 100755 index 00000000..c6699ea3 --- /dev/null +++ b/stages/org.osbuild.grub2.inst @@ -0,0 +1,299 @@ +#!/usr/bin/python3 +""" +Install the grub2 boot loader for non-UEFI systems or hybrid boot + +This stage can be used to generate a grub2 core image and install +it to the correct location to enable booting of non-UEFI systems, +i.e. x86 legacy and PPC64LE (Open Firmware). + +On x86, the core image can be installed into the MBR gap or to a +dedicated BIOS boot partition when the partition label is GTP. On +ppc64le with Open Firmware a dedicated 'PrEP partition' is used. + +x86 / MBR gap: + For historic and performance reasons the first partition + is aligned to a specific sector number (used to be 64, + now it is 2048), which leaves a gap between it and the MBR, + where the core image can be embedded in + +x86 / BIOS boot: + A dedicated partition with a specific GUID[1] is used. + +ppc64le / Open Firmware: + A dedicated partition with a specified GUID[2] is used. + + On ppc64le with Open Firmware a special partition called + 'PrEP partition' is used the store the grub2 core; the + firmware looks for this partition and directly loads and + executes the core form it. + +On x86, a "boot image", aka grub stage 1, is installed into the +master boot record (MBR) of the partition (even in the case the +partition layout is GPT). It main purpose is to load the second +stage (core image). Therefore the location of the core image is +patched into the boot image. + +On ppc64le, the firmware itself directly loads the complete core +image and transfers control to it. + +[1] 21686148-6449-6E6F-744E-656564454649 +[2] 9E1A2D38-C612-4316-AA26-8B49521E5A8B +""" + + +import os +import subprocess +import shutil +import struct +import sys + +from typing import BinaryIO, Dict + +import osbuild.api + + +SCHEMA = r""" +"definitions": { + "core-mkimage": { + "type": "object", + "description": "Generate the core image via grub-mkimage", + "additionalProperties": false, + "required": ["type", "partlabel", "filesystem"], + "properties": { + "type": { + "enum": ["mkimage"] + }, + "partlabel": { + "type": "string", + "enum": ["gpt", "dos"] + }, + "filesystem": { + "type": "string", + "enum": ["ext4", "xfs", "btrfs"] + } + } + }, + "prefix-partition": { + "type": "object", + "description": "Grub2 config on a specific partition, e.g. (,gpt3)/boot", + "additionalProperties": false, + "required": ["type", "partlabel", "number", "path"], + "properties": { + "type": { + "enum": ["partition"] + }, + "partlabel": { + "type": "string", + "enum": ["gpt", "dos"] + }, + "number": { + "description": "The partition number, starting at zero", + "type": "number" + }, + "path": { + "description": "location of grub config inside the partition", + "type": "string", + "pattern": "\/.*" + } + } + } +}, +"additionalProperties": false, +"required": ["filename", "platform", "core", "prefix"], +"properties": { + "filename": { + "type": "string", + "description": "filename of the disk image" + }, + "platform": { + "type": "string", + "description": "Platform of the target system" + }, + "location": { + "type": "integer", + "description": "Location of the stage 2 (in sectors)" + }, + "core": { + "description": "How to obtain the GRUB core image", + "oneOf": [ + {"$ref": "#/definitions/core-mkimage"} + ] + }, + "prefix": { + "description": "location of grub config", + "oneOf": [ + {"$ref": "#/definitions/prefix-partition"} + ] + }, + "sector-size": { + "type": "number", + "description": "Sector size (in bytes)", + "default": 512 + } +} +""" + + +def grub2_partition_id(label): + """grub2 partition identifier for the partition table""" + + label2grub = { + "mbr": "msdos", + "dos": "msdos", + "gpt": "gpt" + } + + if label not in label2grub: + raise ValueError(f"Unknown partition type: {label}") + + return label2grub[label] + + +def patch_bios_boot(image_f, location, sector_size): + # The core image needs to know from where to load its + # second sector so that information needs to be embedded + # into the image itself at the right location, i.e. + # the "sector start parameter" ("size .long 2, 0"): + # 0x200 - GRUB_BOOT_MACHINE_LIST_SIZE (12) = 0x1F4 = 500 + dest = location * sector_size + 500 + print(f"sector start param: {dest}") + image_f.seek(dest) + image_f.write(struct.pack(" 512: + # When installing outside the MBR gap on i386, it means + # that the special BIOS boot partition is used; in that + # case the core location needs to be patched. + patch_bios_boot(image_f, location, sector_size) + + # On x86, the boot image just jumps to core image + with open(boot_path, "rb") as boot_f: + write_boot_image(boot_f, image_f, location) + + return 0 + + +if __name__ == '__main__': + args = osbuild.api.arguments() + r = main(args["tree"], args["options"]) + sys.exit(r)