stages/grub2: support identifying fs via labels

In addition to support for identifying file-systems via their uuids,
they now can be identified via their label as well. Two new options
are introduce for this: `rootfs` and `bootfs` for the root and boot
file system. The latter is option in the case a separated partition
is used for /boot. Both options are an object that can either have
`uuid` or `label` set. The old uuid based options, `root_fs_uuid` &
`boot_fs_uuid` are still supported for now.
Additionally, remove the `GRUB2_ROOT_FS_UUID` option from the
grubenv file and directly write the root file system identifier into
the grub config file.
This commit is contained in:
Christian Kellner 2020-04-17 11:02:58 +02:00 committed by Tom Gundersen
parent d5575edbc7
commit 22d131a5d9

View file

@ -9,8 +9,12 @@ STAGE_DESC = "Configure GRUB2 bootloader and set boot options"
STAGE_INFO = """
Configure the system to use GRUB2 as the bootloader, and set boot options.
Sets the GRUB2 boot/root filesystem to `root_fs_uuid` and sets kernel boot
arguments to "root=UUID={root_fs_uuid} {kernel_opts}".
Sets the GRUB2 boot/root filesystem to `rootfs`. If a separated boot
partition is used it can be specified via `bootfs`. The file-systems
can be identified either via uuid (`{"uuid": "<uuid>"}`) or label
(`{"label": "<label>"}`). The kernel boot argument will be composed
of the root file system id and additional options specified in
`{kernel_opts}`, if any.
Configures GRUB2 to boot via the Boot Loader Specification
(https://systemd.io/BOOT_LOADER_SPECIFICATION), which is the default
@ -38,8 +42,41 @@ and accompanying data can be installed from the built root via `uefi.install`.
Both UEFI and Legacy can be specified at the same time.
"""
STAGE_OPTS = """
"required": ["root_fs_uuid"],
"oneOf": [{
"required": ["root_fs_uuid"]
}, {
"required": ["rootfs"]
}],
"definitions": {
"filesystem": {
"description": "Description of how to locate a file system",
"type": "object",
"oneOf": [{
"required": ["uuid"]
}, {
"required": ["label"]
}],
"properties": {
"label": {
"description": "Identify the file system by label",
"type": "string"
},
"uuid": {
"description": "Identify the file system by UUID",
"type": "string",
"oneOf": [
{ "pattern": "^[0-9A-Za-z]{8}(-[0-9A-Za-z]{4}){3}-[0-9A-Za-z]{12}$",
"examples": ["9c6ae55b-cf88-45b8-84e8-64990759f39d"] },
{ "pattern": "^[0-9A-Za-z]{4}-[0-9A-Za-z]{4}$",
"examples": ["6699-AFB5"] }
]
}
}
}
},
"properties": {
"rootfs": { "$ref": "#/definitions/filesystem" },
"bootfs": { "$ref": "#/definitions/filesystem" },
"root_fs_uuid": {
"description": "UUID of the root filesystem image",
"type": "string",
@ -99,6 +136,14 @@ STAGE_OPTS = """
"""
def fs_spec_decode(spec):
for key in ["uuid", "label"]:
val = spec.get(key)
if val:
return key.upper(), val
raise ValueError("unknown filesystem type")
def copy_modules(tree, platform):
"""Copy all modules from the build image to /boot"""
target = f"{tree}/boot/grub2/{platform}"
@ -128,12 +173,18 @@ def copy_efi_data(tree, vendor):
symlinks=False)
def write_grub_cfg(tree, path):
"""Write the grub config"""
def write_grub_cfg(tree, path, grub_fs):
"""Write the grub config to `tree` at `path`"""
fs_type, fs_id = fs_spec_decode(grub_fs)
type2opt = {
"UUID": "--fs-uuid",
"LABEL": "--label"
}
search = type2opt[fs_type] + " " + fs_id
with open(os.path.join(tree, path), "w") as cfg:
cfg.write("set timeout=0\n"
"load_env\n"
"search --no-floppy --fs-uuid --set=root ${GRUB2_ROOT_FS_UUID}\n"
f"search --no-floppy --set=root {search}\n"
"set boot=${root}\n"
"function load_video {\n"
" insmod all_video\n"
@ -152,13 +203,20 @@ def write_grub_cfg_redirect(tree, path, separate_boot):
def main(tree, options):
root_fs_uuid = options["root_fs_uuid"]
boot_fs_uuid = options.get("boot_fs_uuid", None)
root_fs = options.get("rootfs")
boot_fs = options.get("bootfs")
kernel_opts = options.get("kernel_opts", "")
legacy = options.get("legacy", None)
uefi = options.get("uefi", None)
write_defaults = options.get("write_defaults", True)
# backwards compatibility
if not root_fs:
root_fs = {"uuid": options["root_fs_uuid"]}
if not boot_fs and "boot_fs_uuid" in options:
boot_fs = {"uuid": options["boot_fs_uuid"]}
# legacy boolean means the
if isinstance(legacy, bool) and legacy:
legacy = "i386-pc"
@ -170,9 +228,10 @@ def main(tree, options):
# /boot/grub2 and will not have a grubenv itself.
hybrid = uefi and legacy
# grub_fs_uuid points to the filesystem containing the grub files
grub_fs_uuid = boot_fs_uuid or root_fs_uuid
separate_boot = boot_fs_uuid is not None
# grub_fs points to the filesystem containing the grub files, which is
# either a separate partition (boot_fs) or the root file system (root_fs)
grub_fs = boot_fs or root_fs
separate_boot = boot_fs is not None
# Create the configuration file that determines how grub.cfg is generated.
if write_defaults:
@ -194,9 +253,9 @@ def main(tree, options):
pass
with open(grubenv, "w") as env:
fs_type, fs_id = fs_spec_decode(root_fs)
env.write("# GRUB Environment Block\n"
f"GRUB2_ROOT_FS_UUID={grub_fs_uuid}\n"
f"kernelopts=root=UUID={root_fs_uuid} {kernel_opts}\n")
f"kernelopts=root={fs_type}={fs_id} {kernel_opts}\n")
if uefi is not None:
# UEFI support:
@ -219,10 +278,10 @@ def main(tree, options):
if hybrid:
write_grub_cfg_redirect(tree, grubcfg, separate_boot)
else:
write_grub_cfg(tree, grubcfg)
write_grub_cfg(tree, grubcfg, grub_fs)
if legacy:
write_grub_cfg(tree, "boot/grub2/grub.cfg")
write_grub_cfg(tree, "boot/grub2/grub.cfg", grub_fs)
copy_modules(tree, legacy)
copy_font(tree)