mirror of
https://git.launchpad.net/livecd-rootfs
synced 2026-02-17 15:33:32 +00:00
Add Python boot configuration package
Add architecture-specific boot configurators that translate the debian-cd boot shell scripts (boot-amd64, boot-arm64, boot-ppc64el, boot-riscv64, boot-s390x) into Python. The package uses a class hierarchy: - BaseBootConfigurator: abstract base with common functionality - GrubBootConfigurator: shared GRUB config generation - UEFIBootConfigurator: UEFI-specific shim/ESP handling - Architecture classes: AMD64, ARM64, PPC64EL, RISCV64, S390X A factory function make_boot_configurator_for_arch() creates the appropriate configurator for each architecture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6a6b00d68b
commit
edf0acbeac
43
live-build/isobuilder/boot/__init__.py
Normal file
43
live-build/isobuilder/boot/__init__.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""Boot configuration package for ISO builder.
|
||||||
|
|
||||||
|
This package contains architecture-specific boot configurators for building
|
||||||
|
bootable ISOs for different architectures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..apt_state import AptStateManager
|
||||||
|
from ..builder import Logger
|
||||||
|
from .base import BaseBootConfigurator
|
||||||
|
|
||||||
|
|
||||||
|
def make_boot_configurator_for_arch(
|
||||||
|
arch: str,
|
||||||
|
logger: "Logger",
|
||||||
|
apt_state: "AptStateManager",
|
||||||
|
iso_root: pathlib.Path,
|
||||||
|
) -> "BaseBootConfigurator":
|
||||||
|
"""Factory function to create boot configurator for a specific architecture."""
|
||||||
|
from .amd64 import AMD64BootConfigurator
|
||||||
|
from .arm64 import ARM64BootConfigurator
|
||||||
|
from .ppc64el import PPC64ELBootConfigurator
|
||||||
|
from .riscv64 import RISCV64BootConfigurator
|
||||||
|
from .s390x import S390XBootConfigurator
|
||||||
|
|
||||||
|
if arch == "amd64":
|
||||||
|
return AMD64BootConfigurator(logger, apt_state, iso_root)
|
||||||
|
elif arch == "arm64":
|
||||||
|
return ARM64BootConfigurator(logger, apt_state, iso_root)
|
||||||
|
elif arch == "ppc64el":
|
||||||
|
return PPC64ELBootConfigurator(logger, apt_state, iso_root)
|
||||||
|
elif arch == "riscv64":
|
||||||
|
return RISCV64BootConfigurator(logger, apt_state, iso_root)
|
||||||
|
elif arch == "s390x":
|
||||||
|
return S390XBootConfigurator(logger, apt_state, iso_root)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported architecture: {arch}")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["make_boot_configurator_for_arch"]
|
||||||
201
live-build/isobuilder/boot/amd64.py
Normal file
201
live-build/isobuilder/boot/amd64.py
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
"""AMD64/x86_64 architecture boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from .uefi import UEFIBootConfigurator
|
||||||
|
from .base import default_kernel_params
|
||||||
|
|
||||||
|
|
||||||
|
class AMD64BootConfigurator(UEFIBootConfigurator):
|
||||||
|
"""Boot setup for AMD64/x86_64 architecture."""
|
||||||
|
|
||||||
|
efi_suffix = "x64"
|
||||||
|
grub_target = "x86_64"
|
||||||
|
arch = "amd64"
|
||||||
|
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
# Boring mkisofs options that should be set somewhere architecture independent.
|
||||||
|
opts: list[str | pathlib.Path] = ["-J", "-joliet-long", "-l"]
|
||||||
|
|
||||||
|
# Generalities on booting
|
||||||
|
#
|
||||||
|
# There is a 2x2 matrix of boot modes we care about: legacy or UEFI
|
||||||
|
# boot modes and having the installer be on a cdrom or a disk. Booting
|
||||||
|
# from cdrom uses the el torito standard and booting from disk expects
|
||||||
|
# a MBR or GPT partition table.
|
||||||
|
#
|
||||||
|
# https://wiki.osdev.org/El-Torito has a lot more background on this.
|
||||||
|
|
||||||
|
# ## Set up the mkisofs options for legacy boot.
|
||||||
|
|
||||||
|
# Set the el torito boot image "name", i.e. the path on the ISO
|
||||||
|
# containing the bootloader for legacy-cdrom boot.
|
||||||
|
opts.extend(["-b", "boot/grub/i386-pc/eltorito.img"])
|
||||||
|
|
||||||
|
# Back in the day, el torito booting worked by emulating a floppy
|
||||||
|
# drive. This hasn't been a useful way of operating for a long time.
|
||||||
|
opts.append("-no-emul-boot")
|
||||||
|
|
||||||
|
# Misc options to make the legacy-cdrom boot work.
|
||||||
|
opts.extend(["-boot-load-size", "4", "-boot-info-table", "--grub2-boot-info"])
|
||||||
|
|
||||||
|
# The bootloader to write to the MBR for legacy-disk boot.
|
||||||
|
#
|
||||||
|
# We use the grub stage1 bootloader, boot_hybrid.img, which then jumps
|
||||||
|
# to the eltorito image based on the information xorriso provides it
|
||||||
|
# via the --grub2-boot-info option.
|
||||||
|
opts.extend(
|
||||||
|
[
|
||||||
|
"--grub2-mbr",
|
||||||
|
self.grub_dir.joinpath("usr/lib/grub/i386-pc/boot_hybrid.img"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# ## Set up the mkisofs options for UEFI boot.
|
||||||
|
opts.extend(self.get_uefi_mkisofs_opts())
|
||||||
|
|
||||||
|
# ## Add cd-boot-tree to the ISO
|
||||||
|
opts.append(str(self.boot_tree))
|
||||||
|
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
with self.logger.logged("extracting AMD64 boot files"):
|
||||||
|
|
||||||
|
# Extract UEFI files (common with ARM64)
|
||||||
|
self.extract_uefi_files()
|
||||||
|
|
||||||
|
# AMD64-specific: Add BIOS/legacy boot files
|
||||||
|
with self.logger.logged("adding BIOS/legacy boot files"):
|
||||||
|
self.download_and_extract_package("grub-pc-bin", self.grub_dir)
|
||||||
|
|
||||||
|
grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "i386-pc")
|
||||||
|
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "i386-pc")
|
||||||
|
|
||||||
|
shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir)
|
||||||
|
|
||||||
|
self.copy_grub_modules(
|
||||||
|
src_grub_dir, grub_boot_dir, ["*.mod", "*.lst", "*.o"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_grub_config(self) -> None:
|
||||||
|
"""Generate grub.cfg and loopback.cfg for the boot tree."""
|
||||||
|
# Generate grub.cfg
|
||||||
|
kernel_params = default_kernel_params(self.project)
|
||||||
|
|
||||||
|
boot_grub_dir = self.boot_tree.joinpath("boot", "grub")
|
||||||
|
boot_grub_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
grub_cfg = boot_grub_dir.joinpath("grub.cfg")
|
||||||
|
|
||||||
|
# Write common GRUB header
|
||||||
|
self.write_grub_header(grub_cfg)
|
||||||
|
|
||||||
|
# Main menu entry
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "Try or Install {self.humanproject}" {{
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/vmlinuz {kernel_params}
|
||||||
|
initrd /casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# All but server get safe-graphics mode
|
||||||
|
if self.project != "ubuntu-server":
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "{self.humanproject} (safe graphics)" {{
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/vmlinuz nomodeset {kernel_params}
|
||||||
|
initrd /casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# ubiquity based projects get OEM mode
|
||||||
|
if "maybe-ubiquity" in kernel_params:
|
||||||
|
oem_kernel_params = kernel_params.replace(
|
||||||
|
"maybe-ubiquity", "only-ubiquity oem-config/enable=true"
|
||||||
|
)
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "OEM install (for manufacturers)" {{
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/vmlinuz {oem_kernel_params}
|
||||||
|
initrd /casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Calamares-based projects get OEM mode
|
||||||
|
if self.project in ["lubuntu", "kubuntu"]:
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "OEM install (for manufacturers)" {{
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/vmlinuz {kernel_params} oem-config/enable=true
|
||||||
|
initrd /casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Currently only server is built with HWE, hence no safe-graphics/OEM
|
||||||
|
if self.hwe:
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "{self.humanproject} with the HWE kernel" {{
|
||||||
|
set gfxpayload=keep
|
||||||
|
linux /casper/hwe-vmlinuz {kernel_params}
|
||||||
|
initrd /casper/hwe-initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the loopback config, based on the main config
|
||||||
|
with grub_cfg.open("r") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# sed: delete from line 1 to menu_color_highlight, delete from
|
||||||
|
# grub_platform to end and replace '---' with
|
||||||
|
# 'iso-scan/filename=${iso_path} ---' in lines with 'linux'
|
||||||
|
lines = content.split("\n")
|
||||||
|
start_idx = 0
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if "menu_color_highlight" in line:
|
||||||
|
start_idx = i + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
end_idx = len(lines)
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if "grub_platform" in line:
|
||||||
|
end_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
loopback_lines = lines[start_idx:end_idx]
|
||||||
|
loopback_lines = [
|
||||||
|
(
|
||||||
|
line.replace("---", "iso-scan/filename=${iso_path} ---")
|
||||||
|
if "linux" in line
|
||||||
|
else line
|
||||||
|
)
|
||||||
|
for line in loopback_lines
|
||||||
|
]
|
||||||
|
|
||||||
|
loopback_cfg = boot_grub_dir.joinpath("loopback.cfg")
|
||||||
|
with loopback_cfg.open("w") as f:
|
||||||
|
f.write("\n".join(loopback_lines))
|
||||||
|
|
||||||
|
# UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support)
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write("grub_platform\n")
|
||||||
|
f.write('if [ "$grub_platform" = "efi" ]; then\n')
|
||||||
|
|
||||||
|
self.write_uefi_menu_entries(grub_cfg)
|
||||||
|
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write("fi\n")
|
||||||
81
live-build/isobuilder/boot/arm64.py
Normal file
81
live-build/isobuilder/boot/arm64.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""ARM 64-bit architecture boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from .uefi import UEFIBootConfigurator
|
||||||
|
from .base import default_kernel_params
|
||||||
|
|
||||||
|
|
||||||
|
class ARM64BootConfigurator(UEFIBootConfigurator):
|
||||||
|
"""Boot setup for ARM 64-bit architecture."""
|
||||||
|
|
||||||
|
efi_suffix = "aa64"
|
||||||
|
grub_target = "arm64"
|
||||||
|
arch = "arm64"
|
||||||
|
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return mkisofs options for ARM64."""
|
||||||
|
opts: list[str | pathlib.Path] = [
|
||||||
|
"-J",
|
||||||
|
"-joliet-long",
|
||||||
|
"-l",
|
||||||
|
"-c",
|
||||||
|
"boot/boot.cat",
|
||||||
|
]
|
||||||
|
# Add common UEFI options
|
||||||
|
opts.extend(self.get_uefi_mkisofs_opts())
|
||||||
|
# ARM64-specific: partition cylinder alignment
|
||||||
|
opts.extend(["-partition_cyl_align", "all"])
|
||||||
|
opts.append(self.boot_tree)
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
"""Download and extract bootloader packages for ARM64."""
|
||||||
|
with self.logger.logged("extracting ARM64 boot files"):
|
||||||
|
self.extract_uefi_files()
|
||||||
|
|
||||||
|
def generate_grub_config(self) -> None:
|
||||||
|
"""Generate grub.cfg for ARM64."""
|
||||||
|
kernel_params = default_kernel_params(self.project)
|
||||||
|
|
||||||
|
grub_cfg = self.grub_dir.joinpath("grub.cfg")
|
||||||
|
|
||||||
|
# Write common GRUB header
|
||||||
|
self.write_grub_header(grub_cfg)
|
||||||
|
|
||||||
|
# ARM64-specific: Snapdragon workarounds
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
"""set cmdline=
|
||||||
|
smbios --type 4 --get-string 5 --set proc_version
|
||||||
|
regexp "Snapdragon.*" "$proc_version"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
# Work around Snapdragon X firmware bug. cutmem is not allowed in lockdown mode.
|
||||||
|
if [ $lockdown != "y" ]; then
|
||||||
|
cutmem 0x8800000000 0x8fffffffff
|
||||||
|
fi
|
||||||
|
# arm64.nopauth works around 8cx Gen 3 firmware bug
|
||||||
|
cmdline="clk_ignore_unused pd_ignore_unused arm64.nopauth"
|
||||||
|
fi
|
||||||
|
|
||||||
|
menuentry "Try or Install {self.humanproject}" {{
|
||||||
|
\tset gfxpayload=keep
|
||||||
|
\tlinux\t/casper/vmlinuz $cmdline {kernel_params} console=tty0
|
||||||
|
\tinitrd\t/casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# HWE kernel option if available
|
||||||
|
self.write_hwe_menu_entry(
|
||||||
|
grub_cfg,
|
||||||
|
"vmlinuz",
|
||||||
|
f"{kernel_params} console=tty0",
|
||||||
|
extra_params="$cmdline ",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Note: ARM64 HWE also includes $dtb in the original shell script,
|
||||||
|
# but it's not actually set anywhere in the grub.cfg, so we omit it here
|
||||||
|
|
||||||
|
# UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed)
|
||||||
|
self.write_uefi_menu_entries(grub_cfg)
|
||||||
111
live-build/isobuilder/boot/base.py
Normal file
111
live-build/isobuilder/boot/base.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""Base classes and helper functions for boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
from ..builder import Logger
|
||||||
|
from ..apt_state import AptStateManager
|
||||||
|
|
||||||
|
|
||||||
|
def default_kernel_params(project: str) -> str:
|
||||||
|
if project == "ubuntukylin":
|
||||||
|
return (
|
||||||
|
"file=/cdrom/preseed/ubuntu.seed locale=zh_CN "
|
||||||
|
"keyboard-configuration/layoutcode?=cn quiet splash --- "
|
||||||
|
)
|
||||||
|
if project == "ubuntu-server":
|
||||||
|
return " --- "
|
||||||
|
else:
|
||||||
|
return " --- quiet splash"
|
||||||
|
|
||||||
|
|
||||||
|
class BaseBootConfigurator(ABC):
|
||||||
|
"""Abstract base class for architecture-specific boot configurators.
|
||||||
|
|
||||||
|
Subclasses must implement:
|
||||||
|
- extract_files(): Download and extract bootloader packages
|
||||||
|
- mkisofs_opts(): Return mkisofs command-line options
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
logger: Logger,
|
||||||
|
apt_state: AptStateManager,
|
||||||
|
iso_root: pathlib.Path,
|
||||||
|
) -> None:
|
||||||
|
self.logger = logger
|
||||||
|
self.apt_state = apt_state
|
||||||
|
self.iso_root = iso_root
|
||||||
|
|
||||||
|
def create_dirs(self, workdir):
|
||||||
|
self.scratch = workdir.joinpath("boot-stuff")
|
||||||
|
self.scratch.mkdir(exist_ok=True)
|
||||||
|
self.boot_tree = self.scratch.joinpath("cd-boot-tree")
|
||||||
|
|
||||||
|
def download_and_extract_package(
|
||||||
|
self, pkg_name: str, target_dir: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
"""Download a Debian package and extract its contents to target directory."""
|
||||||
|
self.logger.log(f"downloading and extracting {pkg_name}")
|
||||||
|
target_dir.mkdir(exist_ok=True, parents=True)
|
||||||
|
with tempfile.TemporaryDirectory() as tdir_str:
|
||||||
|
tdir = pathlib.Path(tdir_str)
|
||||||
|
self.apt_state.download_direct(pkg_name, tdir)
|
||||||
|
[deb] = tdir.glob("*.deb")
|
||||||
|
dpkg_proc = subprocess.Popen(
|
||||||
|
["dpkg-deb", "--fsys-tarfile", deb], stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
tar_proc = subprocess.Popen(
|
||||||
|
["tar", "xf", "-", "-C", target_dir], stdin=dpkg_proc.stdout
|
||||||
|
)
|
||||||
|
assert dpkg_proc.stdout is not None
|
||||||
|
dpkg_proc.stdout.close()
|
||||||
|
tar_proc.communicate()
|
||||||
|
|
||||||
|
def copy_grub_modules(
|
||||||
|
self, src_dir: pathlib.Path, dest_dir: pathlib.Path, extensions: list[str]
|
||||||
|
) -> None:
|
||||||
|
"""Copy GRUB module files matching given extensions from src to dest."""
|
||||||
|
for ext in extensions:
|
||||||
|
for file in src_dir.glob(ext):
|
||||||
|
shutil.copy(file, dest_dir)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
"""Download and extract bootloader packages to the boot tree.
|
||||||
|
|
||||||
|
Each architecture must implement this to set up its specific bootloader files.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return mkisofs command-line options for this architecture.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of command-line options to pass to mkisofs/xorriso.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
||||||
|
"""Post-process the ISO image after xorriso creates it."""
|
||||||
|
|
||||||
|
def make_bootable(
|
||||||
|
self,
|
||||||
|
workdir: pathlib.Path,
|
||||||
|
project: str,
|
||||||
|
capproject: str,
|
||||||
|
subarch: str,
|
||||||
|
hwe: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Make the ISO bootable by extracting bootloader files."""
|
||||||
|
self.project = project
|
||||||
|
self.humanproject = capproject.replace("-", " ")
|
||||||
|
self.subarch = subarch
|
||||||
|
self.hwe = hwe
|
||||||
|
self.create_dirs(workdir)
|
||||||
|
with self.logger.logged("configuring boot"):
|
||||||
|
self.extract_files()
|
||||||
103
live-build/isobuilder/boot/grub.py
Normal file
103
live-build/isobuilder/boot/grub.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"""GRUB boot configuration for multiple architectures."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from .base import BaseBootConfigurator
|
||||||
|
|
||||||
|
|
||||||
|
def copy_grub_common_files_to_boot_tree(
|
||||||
|
grub_dir: pathlib.Path, boot_tree: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
fonts_dir = boot_tree.joinpath("boot", "grub", "fonts")
|
||||||
|
fonts_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
src = grub_dir.joinpath("usr", "share", "grub", "unicode.pf2")
|
||||||
|
dst = fonts_dir.joinpath("unicode.pf2")
|
||||||
|
shutil.copy(src, dst)
|
||||||
|
|
||||||
|
|
||||||
|
class GrubBootConfigurator(BaseBootConfigurator):
|
||||||
|
"""Base class for architectures that use GRUB (all except S390X).
|
||||||
|
|
||||||
|
Common GRUB functionality shared across AMD64, ARM64, PPC64EL, and RISC-V64.
|
||||||
|
Subclasses must implement generate_grub_config().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_dirs(self, workdir):
|
||||||
|
super().create_dirs(workdir)
|
||||||
|
self.grub_dir = self.boot_tree.joinpath("grub")
|
||||||
|
|
||||||
|
def setup_grub_common_files(self) -> None:
|
||||||
|
"""Copy common GRUB files (fonts, etc.) to boot tree."""
|
||||||
|
copy_grub_common_files_to_boot_tree(self.grub_dir, self.boot_tree)
|
||||||
|
|
||||||
|
def write_grub_header(
|
||||||
|
self, grub_cfg: pathlib.Path, include_loadfont: bool = True
|
||||||
|
) -> None:
|
||||||
|
"""Write common GRUB config header (timeout, colors).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
grub_cfg: Path to grub.cfg file
|
||||||
|
include_loadfont: Whether to include 'loadfont unicode'
|
||||||
|
(not needed for RISC-V)
|
||||||
|
"""
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write("set timeout=30\n\n")
|
||||||
|
if include_loadfont:
|
||||||
|
f.write("loadfont unicode\n\n")
|
||||||
|
f.write(
|
||||||
|
"""set menu_color_normal=white/black
|
||||||
|
set menu_color_highlight=black/light-gray
|
||||||
|
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_hwe_menu_entry(
|
||||||
|
self,
|
||||||
|
grub_cfg: pathlib.Path,
|
||||||
|
kernel_name: str,
|
||||||
|
kernel_params: str,
|
||||||
|
extra_params: str = "",
|
||||||
|
) -> None:
|
||||||
|
"""Write HWE kernel menu entry if HWE is enabled.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
grub_cfg: Path to grub.cfg file
|
||||||
|
kernel_name: Kernel binary name (vmlinuz or vmlinux)
|
||||||
|
kernel_params: Kernel parameters to append
|
||||||
|
extra_params: Additional parameters (e.g., console=tty0, $cmdline)
|
||||||
|
"""
|
||||||
|
if self.hwe:
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "{self.humanproject} with the HWE kernel" {{
|
||||||
|
\tset gfxpayload=keep
|
||||||
|
\tlinux\t/casper/hwe-{kernel_name} {extra_params}{kernel_params}
|
||||||
|
\tinitrd\t/casper/hwe-initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def generate_grub_config(self) -> None:
|
||||||
|
"""Generate grub.cfg configuration file.
|
||||||
|
|
||||||
|
Each GRUB-based architecture must implement this to create its
|
||||||
|
specific GRUB configuration.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def make_bootable(
|
||||||
|
self,
|
||||||
|
workdir: pathlib.Path,
|
||||||
|
project: str,
|
||||||
|
capproject: str,
|
||||||
|
subarch: str,
|
||||||
|
hwe: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Make the ISO bootable by extracting files and generating GRUB config."""
|
||||||
|
super().make_bootable(workdir, project, capproject, subarch, hwe)
|
||||||
|
with self.logger.logged("generating grub config"):
|
||||||
|
self.generate_grub_config()
|
||||||
75
live-build/isobuilder/boot/ppc64el.py
Normal file
75
live-build/isobuilder/boot/ppc64el.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""PowerPC 64-bit Little Endian architecture boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from .grub import GrubBootConfigurator
|
||||||
|
from .base import default_kernel_params
|
||||||
|
|
||||||
|
|
||||||
|
class PPC64ELBootConfigurator(GrubBootConfigurator):
|
||||||
|
"""Boot setup for PowerPC 64-bit Little Endian architecture."""
|
||||||
|
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return mkisofs options for PPC64EL."""
|
||||||
|
# Add cd-boot-tree to the ISO
|
||||||
|
return [self.boot_tree]
|
||||||
|
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
"""Download and extract bootloader packages for PPC64EL."""
|
||||||
|
self.logger.log("extracting PPC64EL boot files")
|
||||||
|
|
||||||
|
# Download and extract bootloader packages
|
||||||
|
self.download_and_extract_package("grub2-common", self.grub_dir)
|
||||||
|
self.download_and_extract_package("grub-ieee1275-bin", self.grub_dir)
|
||||||
|
|
||||||
|
# Add common files for GRUB to tree
|
||||||
|
self.setup_grub_common_files()
|
||||||
|
|
||||||
|
# Add IEEE1275 ppc boot files
|
||||||
|
ppc_dir = self.boot_tree.joinpath("ppc")
|
||||||
|
ppc_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "powerpc-ieee1275")
|
||||||
|
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275")
|
||||||
|
|
||||||
|
# Copy bootinfo.txt to ppc directory
|
||||||
|
shutil.copy(
|
||||||
|
src_grub_dir.joinpath("bootinfo.txt"), ppc_dir.joinpath("bootinfo.txt")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy eltorito.elf to boot/grub as powerpc.elf
|
||||||
|
shutil.copy(
|
||||||
|
src_grub_dir.joinpath("eltorito.elf"),
|
||||||
|
self.boot_tree.joinpath("boot", "grub", "powerpc.elf"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy GRUB modules
|
||||||
|
self.copy_grub_modules(src_grub_dir, grub_boot_dir, ["*.mod", "*.lst"])
|
||||||
|
|
||||||
|
def generate_grub_config(self) -> None:
|
||||||
|
"""Generate grub.cfg for PPC64EL."""
|
||||||
|
kernel_params = default_kernel_params(self.project)
|
||||||
|
|
||||||
|
grub_cfg = self.grub_dir.joinpath("grub.cfg")
|
||||||
|
|
||||||
|
# Write common GRUB header
|
||||||
|
self.write_grub_header(grub_cfg)
|
||||||
|
|
||||||
|
# Main menu entry
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "Try or Install {self.humanproject}" {{
|
||||||
|
\tset gfxpayload=keep
|
||||||
|
\tlinux\t/casper/vmlinux quiet {kernel_params}
|
||||||
|
\tinitrd\t/casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# HWE kernel option if available
|
||||||
|
self.write_hwe_menu_entry(
|
||||||
|
grub_cfg, "vmlinux", kernel_params, extra_params="quiet "
|
||||||
|
)
|
||||||
223
live-build/isobuilder/boot/riscv64.py
Normal file
223
live-build/isobuilder/boot/riscv64.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
"""RISC-V 64-bit architecture boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from .grub import GrubBootConfigurator
|
||||||
|
|
||||||
|
|
||||||
|
def copy_unsigned_monolithic_grub_to_boot_tree(
|
||||||
|
grub_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
efi_boot_dir = boot_tree.joinpath("EFI", "boot")
|
||||||
|
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
shutil.copy(
|
||||||
|
grub_dir.joinpath(
|
||||||
|
"usr",
|
||||||
|
"lib",
|
||||||
|
"grub",
|
||||||
|
f"{grub_target}-efi",
|
||||||
|
"monolithic",
|
||||||
|
f"gcd{efi_suffix}.efi",
|
||||||
|
),
|
||||||
|
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
||||||
|
)
|
||||||
|
|
||||||
|
grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi")
|
||||||
|
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
|
||||||
|
for mod_file in src_grub_dir.glob("*.mod"):
|
||||||
|
shutil.copy(mod_file, grub_boot_dir)
|
||||||
|
for lst_file in src_grub_dir.glob("*.lst"):
|
||||||
|
shutil.copy(lst_file, grub_boot_dir)
|
||||||
|
|
||||||
|
|
||||||
|
class RISCV64BootConfigurator(GrubBootConfigurator):
|
||||||
|
"""Boot setup for RISC-V 64-bit architecture."""
|
||||||
|
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return mkisofs options for RISC-V64."""
|
||||||
|
efi_img = self.scratch.joinpath("efi.img")
|
||||||
|
|
||||||
|
return [
|
||||||
|
"-joliet",
|
||||||
|
"on",
|
||||||
|
"-compliance",
|
||||||
|
"joliet_long_names",
|
||||||
|
"--append_partition",
|
||||||
|
"2",
|
||||||
|
"0xef",
|
||||||
|
efi_img,
|
||||||
|
"-boot_image",
|
||||||
|
"any",
|
||||||
|
"partition_offset=10240",
|
||||||
|
"-boot_image",
|
||||||
|
"any",
|
||||||
|
"partition_cyl_align=all",
|
||||||
|
"-boot_image",
|
||||||
|
"any",
|
||||||
|
"efi_path=--interval:appended_partition_2:all::",
|
||||||
|
"-boot_image",
|
||||||
|
"any",
|
||||||
|
"appended_part_as=gpt",
|
||||||
|
"-boot_image",
|
||||||
|
"any",
|
||||||
|
"cat_path=/boot/boot.cat",
|
||||||
|
"-fs",
|
||||||
|
"64m",
|
||||||
|
]
|
||||||
|
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
"""Download and extract bootloader packages for RISC-V64."""
|
||||||
|
self.logger.log("extracting RISC-V64 boot files")
|
||||||
|
u_boot_dir = self.scratch.joinpath("u-boot-sifive")
|
||||||
|
|
||||||
|
# Download and extract bootloader packages
|
||||||
|
self.download_and_extract_package("grub2-common", self.grub_dir)
|
||||||
|
self.download_and_extract_package("grub-efi-riscv64-bin", self.grub_dir)
|
||||||
|
self.download_and_extract_package("grub-efi-riscv64-unsigned", self.grub_dir)
|
||||||
|
self.download_and_extract_package("u-boot-sifive", u_boot_dir)
|
||||||
|
|
||||||
|
# Add GRUB to tree
|
||||||
|
self.setup_grub_common_files()
|
||||||
|
copy_unsigned_monolithic_grub_to_boot_tree(
|
||||||
|
self.grub_dir, "riscv64", "riscv64", self.boot_tree
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract DTBs to tree
|
||||||
|
self.logger.log("extracting device tree files")
|
||||||
|
kernel_layer = self.scratch.joinpath("kernel-layer")
|
||||||
|
squashfs_path = self.iso_root.joinpath(
|
||||||
|
"casper", "ubuntu-server-minimal.squashfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract device tree firmware from squashfs
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
"unsquashfs",
|
||||||
|
"-no-xattrs",
|
||||||
|
"-d",
|
||||||
|
kernel_layer,
|
||||||
|
squashfs_path,
|
||||||
|
"usr/lib/firmware",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy DTBs if they exist
|
||||||
|
dtb_dir = self.boot_tree.joinpath("dtb")
|
||||||
|
dtb_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware")
|
||||||
|
device_tree_files = list(firmware_dir.glob("*/device-tree/*"))
|
||||||
|
|
||||||
|
if device_tree_files:
|
||||||
|
for dtb_file in device_tree_files:
|
||||||
|
if dtb_file.is_file():
|
||||||
|
shutil.copy(dtb_file, dtb_dir)
|
||||||
|
|
||||||
|
# Clean up kernel layer
|
||||||
|
shutil.rmtree(kernel_layer)
|
||||||
|
|
||||||
|
# Copy tree contents to live-media rootfs
|
||||||
|
self.logger.run(["cp", "-aT", self.boot_tree, self.iso_root], check=True)
|
||||||
|
|
||||||
|
# Create ESP image with GRUB and dtbs
|
||||||
|
efi_img = self.scratch.joinpath("efi.img")
|
||||||
|
self.logger.run(
|
||||||
|
["mkfs.msdos", "-n", "ESP", "-C", "-v", efi_img, "32768"], check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add EFI files to ESP
|
||||||
|
efi_dir = self.boot_tree.joinpath("EFI")
|
||||||
|
self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True)
|
||||||
|
|
||||||
|
# Add DTBs to ESP
|
||||||
|
self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True)
|
||||||
|
|
||||||
|
def generate_grub_config(self) -> None:
|
||||||
|
"""Generate grub.cfg for RISC-V64."""
|
||||||
|
grub_dir = self.iso_root.joinpath("boot", "grub")
|
||||||
|
grub_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
grub_cfg = grub_dir.joinpath("grub.cfg")
|
||||||
|
|
||||||
|
# Write GRUB header (without loadfont for RISC-V)
|
||||||
|
self.write_grub_header(grub_cfg, include_loadfont=False)
|
||||||
|
|
||||||
|
# Main menu entry
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
f"""menuentry "Try or Install {self.humanproject}" {{
|
||||||
|
\tset gfxpayload=keep
|
||||||
|
\tlinux\t/casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 ---
|
||||||
|
\tinitrd\t/casper/initrd
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
# HWE kernel option if available
|
||||||
|
self.write_hwe_menu_entry(
|
||||||
|
grub_cfg,
|
||||||
|
"vmlinux",
|
||||||
|
"---",
|
||||||
|
extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ",
|
||||||
|
)
|
||||||
|
|
||||||
|
def post_process_iso(self, iso_path: pathlib.Path) -> None:
|
||||||
|
"""Add GPT partitions with U-Boot for SiFive Unmatched board.
|
||||||
|
|
||||||
|
The SiFive Unmatched board needs a GPT table containing U-Boot in
|
||||||
|
order to boot. U-Boot does not currently support booting from a CD,
|
||||||
|
so the GPT table also contains an entry pointing to the ESP so that
|
||||||
|
U-Boot can find it.
|
||||||
|
"""
|
||||||
|
u_boot_dir = self.scratch.joinpath(
|
||||||
|
"u-boot-sifive", "usr", "lib", "u-boot", "sifive_unmatched"
|
||||||
|
)
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
"sgdisk",
|
||||||
|
iso_path,
|
||||||
|
"--set-alignment=2",
|
||||||
|
"-d",
|
||||||
|
"1",
|
||||||
|
"-n",
|
||||||
|
"1:2082:10273",
|
||||||
|
"-c",
|
||||||
|
"1:loader2",
|
||||||
|
"-t",
|
||||||
|
"1:2E54B353-1271-4842-806F-E436D6AF6985",
|
||||||
|
"-n",
|
||||||
|
"3:10274:12321",
|
||||||
|
"-c",
|
||||||
|
"3:loader1",
|
||||||
|
"-t",
|
||||||
|
"3:5B193300-FC78-40CD-8002-E86C45580B47",
|
||||||
|
"-c",
|
||||||
|
"2:ESP",
|
||||||
|
"-r=2:3",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
"dd",
|
||||||
|
f"if={u_boot_dir / 'u-boot.itb'}",
|
||||||
|
f"of={iso_path}",
|
||||||
|
"bs=512",
|
||||||
|
"seek=2082",
|
||||||
|
"conv=notrunc",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
"dd",
|
||||||
|
f"if={u_boot_dir / 'u-boot-spl.bin'}",
|
||||||
|
f"of={iso_path}",
|
||||||
|
"bs=512",
|
||||||
|
"seek=10274",
|
||||||
|
"conv=notrunc",
|
||||||
|
],
|
||||||
|
)
|
||||||
206
live-build/isobuilder/boot/s390x.py
Normal file
206
live-build/isobuilder/boot/s390x.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
"""IBM S/390 architecture boot configuration."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from .base import BaseBootConfigurator
|
||||||
|
|
||||||
|
|
||||||
|
README_dot_boot = """\
|
||||||
|
About the S/390 installation CD
|
||||||
|
===============================
|
||||||
|
|
||||||
|
It is possible to "boot" the installation system off this CD using
|
||||||
|
the files provided in the /boot directory.
|
||||||
|
|
||||||
|
Although you can boot the installer from this CD, the installation
|
||||||
|
itself is *not* actually done from the CD. Once the initrd is loaded,
|
||||||
|
the installer will ask you to configure your network connection and
|
||||||
|
uses the network-console component to allow you to continue the
|
||||||
|
installation over SSH. The rest of the installation is done over the
|
||||||
|
network: all installer components and Debian packages are retrieved
|
||||||
|
from a mirror.
|
||||||
|
|
||||||
|
Instead of SSH, one can also use the ASCII terminal available in HMC.
|
||||||
|
|
||||||
|
Exporting full .iso contents (including the hidden .disk directory)
|
||||||
|
allows one to use the result as a valid mirror for installation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ubuntu_dot_exec = """\
|
||||||
|
/* REXX EXEC TO IPL Ubuntu for */
|
||||||
|
/* z Systems FROM THE VM READER. */
|
||||||
|
/* */
|
||||||
|
'CP CLOSE RDR'
|
||||||
|
'PURGE RDR ALL'
|
||||||
|
'SPOOL PUNCH * RDR'
|
||||||
|
'PUNCH KERNEL UBUNTU * (NOHEADER'
|
||||||
|
'PUNCH PARMFILE UBUNTU * (NOHEADER'
|
||||||
|
'PUNCH INITRD UBUNTU * (NOHEADER'
|
||||||
|
'CHANGE RDR ALL KEEP NOHOLD'
|
||||||
|
'CP IPL 000C CLEAR'
|
||||||
|
"""
|
||||||
|
|
||||||
|
ubuntu_dot_ins = """\
|
||||||
|
* Ubuntu for IBM Z (default kernel)
|
||||||
|
kernel.ubuntu 0x00000000
|
||||||
|
initrd.off 0x0001040c
|
||||||
|
initrd.siz 0x00010414
|
||||||
|
parmfile.ubuntu 0x00010480
|
||||||
|
initrd.ubuntu 0x01000000
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def gen_s390_cd_kernel(
|
||||||
|
kernel: pathlib.Path, initrd: pathlib.Path, cmdline: str, outfile: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
"""Generate a bootable S390X CD kernel image.
|
||||||
|
|
||||||
|
This is a Python translation of gen-s390-cd-kernel.pl from debian-cd.
|
||||||
|
It creates a bootable image for S/390 architecture by combining kernel,
|
||||||
|
initrd, and boot parameters in a specific format.
|
||||||
|
"""
|
||||||
|
# Calculate sizes
|
||||||
|
initrd_size = initrd.stat().st_size
|
||||||
|
|
||||||
|
# The initrd is placed at a fixed offset of 16 MiB
|
||||||
|
initrd_offset = 0x1000000
|
||||||
|
|
||||||
|
# Calculate total boot image size (rounded up to 4K blocks)
|
||||||
|
boot_size = ((initrd_offset + initrd_size) >> 12) + 1
|
||||||
|
boot_size = boot_size << 12
|
||||||
|
|
||||||
|
# Validate cmdline length (max 896 bytes)
|
||||||
|
if len(cmdline) >= 896:
|
||||||
|
raise ValueError(f"Kernel commandline too long ({len(cmdline)} bytes)")
|
||||||
|
|
||||||
|
# Create output file and fill with zeros
|
||||||
|
with outfile.open("wb") as out_fh:
|
||||||
|
# Fill entire file with zeros
|
||||||
|
out_fh.write(b"\x00" * boot_size)
|
||||||
|
|
||||||
|
# Copy kernel to offset 0
|
||||||
|
out_fh.seek(0)
|
||||||
|
with kernel.open("rb") as kernel_fh:
|
||||||
|
out_fh.write(kernel_fh.read())
|
||||||
|
|
||||||
|
# Copy initrd to offset 0x1000000 (16 MiB)
|
||||||
|
out_fh.seek(initrd_offset)
|
||||||
|
with initrd.open("rb") as initrd_fh:
|
||||||
|
out_fh.write(initrd_fh.read())
|
||||||
|
|
||||||
|
# Write boot loader control value at offset 4
|
||||||
|
# This tells the S/390 boot loader where to find the kernel
|
||||||
|
out_fh.seek(4)
|
||||||
|
out_fh.write(struct.pack("!I", 0x80010000))
|
||||||
|
|
||||||
|
# Write kernel command line at offset 0x10480
|
||||||
|
out_fh.seek(0x10480)
|
||||||
|
out_fh.write(cmdline.encode("utf-8"))
|
||||||
|
|
||||||
|
# Write initrd parameters
|
||||||
|
# Initrd offset at 0x1040C
|
||||||
|
out_fh.seek(0x1040C)
|
||||||
|
out_fh.write(struct.pack("!I", initrd_offset))
|
||||||
|
|
||||||
|
# Initrd size at 0x10414
|
||||||
|
out_fh.seek(0x10414)
|
||||||
|
out_fh.write(struct.pack("!I", initrd_size))
|
||||||
|
|
||||||
|
|
||||||
|
class S390XBootConfigurator(BaseBootConfigurator):
|
||||||
|
"""Boot setup for IBM S/390 architecture."""
|
||||||
|
|
||||||
|
def mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return mkisofs options for S390X."""
|
||||||
|
return [
|
||||||
|
"-J",
|
||||||
|
"-no-emul-boot",
|
||||||
|
"-b",
|
||||||
|
"boot/ubuntu.ikr",
|
||||||
|
]
|
||||||
|
|
||||||
|
def extract_files(self) -> None:
|
||||||
|
"""Set up boot files for S390X."""
|
||||||
|
self.logger.log("extracting S390X boot files")
|
||||||
|
boot_dir = self.iso_root.joinpath("boot")
|
||||||
|
boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy static .ins & exec scripts, docs from data directory
|
||||||
|
self.iso_root.joinpath("README.boot").write_text(README_dot_boot)
|
||||||
|
boot_dir.joinpath("ubuntu.exec").write_text(ubuntu_dot_exec)
|
||||||
|
boot_dir.joinpath("ubuntu.ins").write_text(ubuntu_dot_ins)
|
||||||
|
|
||||||
|
# Move kernel image to the name used in .ins & exec scripts
|
||||||
|
kernel_src = self.iso_root.joinpath("casper", "vmlinuz")
|
||||||
|
kernel_dst = boot_dir.joinpath("kernel.ubuntu")
|
||||||
|
kernel_src.replace(kernel_dst)
|
||||||
|
|
||||||
|
# Move initrd to the name used in .ins & exec scripts
|
||||||
|
initrd_src = self.iso_root.joinpath("casper", "initrd")
|
||||||
|
initrd_dst = boot_dir.joinpath("initrd.ubuntu")
|
||||||
|
initrd_src.replace(initrd_dst)
|
||||||
|
|
||||||
|
# Compute initrd offset & size, store in files used by .ins & exec scripts
|
||||||
|
# Offset is always 0x1000000 (16 MiB)
|
||||||
|
initrd_offset_file = boot_dir.joinpath("initrd.off")
|
||||||
|
with initrd_offset_file.open("wb") as f:
|
||||||
|
f.write(struct.pack("!I", 0x1000000))
|
||||||
|
|
||||||
|
# Size is the actual size of the initrd
|
||||||
|
initrd_size = initrd_dst.stat().st_size
|
||||||
|
initrd_size_file = boot_dir.joinpath("initrd.siz")
|
||||||
|
with initrd_size_file.open("wb") as f:
|
||||||
|
f.write(struct.pack("!I", initrd_size))
|
||||||
|
|
||||||
|
# Compute cmdline, store in parmfile used by .ins & exec scripts
|
||||||
|
parmfile = boot_dir.joinpath("parmfile.ubuntu")
|
||||||
|
with parmfile.open("w") as f:
|
||||||
|
f.write(" --- ")
|
||||||
|
|
||||||
|
# Generate secondary top-level ubuntu.ins file
|
||||||
|
# This transforms lines not starting with * by prepending "boot/"
|
||||||
|
ubuntu_ins_src = boot_dir.joinpath("ubuntu.ins")
|
||||||
|
ubuntu_ins_dst = self.iso_root.joinpath("ubuntu.ins")
|
||||||
|
if ubuntu_ins_src.exists():
|
||||||
|
self.logger.run(
|
||||||
|
["sed", "-e", "s,^[^*],boot/&,g", ubuntu_ins_src],
|
||||||
|
stdout=ubuntu_ins_dst.open("w"),
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate QEMU-KVM boot image using gen_s390_cd_kernel
|
||||||
|
cmdline = parmfile.read_text().strip()
|
||||||
|
ikr_file = boot_dir.joinpath("ubuntu.ikr")
|
||||||
|
gen_s390_cd_kernel(kernel_dst, initrd_dst, cmdline, ikr_file)
|
||||||
|
|
||||||
|
# Extract bootloader signing certificate
|
||||||
|
installed_pem = pathlib.Path("/usr/lib/s390-tools/stage3.pem")
|
||||||
|
squashfs_root = self.iso_root.joinpath("squashfs-root")
|
||||||
|
squashfs_path = self.iso_root.joinpath(
|
||||||
|
"casper", "ubuntu-server-minimal.squashfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
if squashfs_path.exists():
|
||||||
|
self.logger.run(
|
||||||
|
[
|
||||||
|
"unsquashfs",
|
||||||
|
"-no-xattrs",
|
||||||
|
"-i",
|
||||||
|
"-d",
|
||||||
|
squashfs_root,
|
||||||
|
squashfs_path,
|
||||||
|
installed_pem,
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Move certificate to iso root
|
||||||
|
cert_src = squashfs_root.joinpath(str(installed_pem).lstrip("/"))
|
||||||
|
cert_dst = self.iso_root.joinpath("ubuntu.pem")
|
||||||
|
if cert_src.exists():
|
||||||
|
cert_src.replace(cert_dst)
|
||||||
|
|
||||||
|
# Clean up squashfs extraction
|
||||||
|
shutil.rmtree(squashfs_root)
|
||||||
168
live-build/isobuilder/boot/uefi.py
Normal file
168
live-build/isobuilder/boot/uefi.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
"""UEFI boot configuration for AMD64 and ARM64 architectures."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from ..builder import Logger
|
||||||
|
from .grub import GrubBootConfigurator
|
||||||
|
|
||||||
|
|
||||||
|
def copy_signed_shim_grub_to_boot_tree(
|
||||||
|
shim_dir: pathlib.Path,
|
||||||
|
grub_dir: pathlib.Path,
|
||||||
|
efi_suffix: str,
|
||||||
|
grub_target: str,
|
||||||
|
boot_tree: pathlib.Path,
|
||||||
|
) -> None:
|
||||||
|
efi_boot_dir = boot_tree.joinpath("EFI", "boot")
|
||||||
|
efi_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
shutil.copy(
|
||||||
|
shim_dir.joinpath("usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"),
|
||||||
|
efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"),
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
shim_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"),
|
||||||
|
efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"),
|
||||||
|
)
|
||||||
|
shutil.copy(
|
||||||
|
grub_dir.joinpath(
|
||||||
|
"usr",
|
||||||
|
"lib",
|
||||||
|
"grub",
|
||||||
|
f"{grub_target}-efi-signed",
|
||||||
|
f"gcd{efi_suffix}.efi.signed",
|
||||||
|
),
|
||||||
|
efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"),
|
||||||
|
)
|
||||||
|
|
||||||
|
grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi")
|
||||||
|
grub_boot_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi")
|
||||||
|
for mod_file in src_grub_dir.glob("*.mod"):
|
||||||
|
shutil.copy(mod_file, grub_boot_dir)
|
||||||
|
for lst_file in src_grub_dir.glob("*.lst"):
|
||||||
|
shutil.copy(lst_file, grub_boot_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def create_eltorito_esp_image(
|
||||||
|
logger: Logger, boot_tree: pathlib.Path, target_file: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
logger.log("creating El Torito ESP image")
|
||||||
|
efi_dir = boot_tree.joinpath("EFI")
|
||||||
|
|
||||||
|
# Calculate size: du -s --apparent-size --block-size=1024 + 1024
|
||||||
|
result = logger.run(
|
||||||
|
["du", "-s", "--apparent-size", "--block-size=1024", efi_dir],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
size_kb = int(result.stdout.split()[0]) + 1024
|
||||||
|
|
||||||
|
# Create filesystem: mkfs.msdos -n ESP -C -v
|
||||||
|
logger.run(
|
||||||
|
["mkfs.msdos", "-n", "ESP", "-C", "-v", target_file, str(size_kb)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy files: mcopy -s -i target_file EFI ::/.
|
||||||
|
logger.run(["mcopy", "-s", "-i", target_file, efi_dir, "::/."], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
class UEFIBootConfigurator(GrubBootConfigurator):
|
||||||
|
"""Base class for UEFI-based architectures (AMD64, ARM64).
|
||||||
|
|
||||||
|
Subclasses should set:
|
||||||
|
- efi_suffix: EFI binary suffix (e.g., "x64", "aa64")
|
||||||
|
- grub_target: GRUB target name (e.g., "x86_64", "arm64")
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Subclasses must override these
|
||||||
|
efi_suffix: str = ""
|
||||||
|
grub_target: str = ""
|
||||||
|
arch: str = ""
|
||||||
|
|
||||||
|
def create_dirs(self, workdir):
|
||||||
|
super().create_dirs(workdir)
|
||||||
|
self.shim_dir = self.boot_tree.joinpath("shim")
|
||||||
|
|
||||||
|
def get_uefi_grub_packages(self) -> list[str]:
|
||||||
|
"""Return list of UEFI GRUB packages to download."""
|
||||||
|
return [
|
||||||
|
"grub2-common",
|
||||||
|
f"grub-efi-{self.arch}-bin",
|
||||||
|
f"grub-efi-{self.arch}-signed",
|
||||||
|
]
|
||||||
|
|
||||||
|
def extract_uefi_files(self) -> None:
|
||||||
|
"""Extract common UEFI files to boot tree."""
|
||||||
|
# Download UEFI packages
|
||||||
|
self.download_and_extract_package("shim-signed", self.shim_dir)
|
||||||
|
for pkg in self.get_uefi_grub_packages():
|
||||||
|
self.download_and_extract_package(pkg, self.grub_dir)
|
||||||
|
|
||||||
|
# Add common files for GRUB to tree
|
||||||
|
self.setup_grub_common_files()
|
||||||
|
|
||||||
|
# Add EFI GRUB to tree
|
||||||
|
copy_signed_shim_grub_to_boot_tree(
|
||||||
|
self.shim_dir,
|
||||||
|
self.grub_dir,
|
||||||
|
self.efi_suffix,
|
||||||
|
self.grub_target,
|
||||||
|
self.boot_tree,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create ESP image for El-Torito catalog and hybrid boot
|
||||||
|
create_eltorito_esp_image(
|
||||||
|
self.logger, self.boot_tree, self.scratch.joinpath("cd-boot-efi.img")
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_uefi_menu_entries(self, grub_cfg: pathlib.Path) -> None:
|
||||||
|
"""Write UEFI firmware menu entries."""
|
||||||
|
with grub_cfg.open("a") as f:
|
||||||
|
f.write(
|
||||||
|
"""menuentry 'Boot from next volume' {
|
||||||
|
\texit 1
|
||||||
|
}
|
||||||
|
menuentry 'UEFI Firmware Settings' {
|
||||||
|
\tfwsetup
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]:
|
||||||
|
"""Return common UEFI mkisofs options."""
|
||||||
|
# To make our ESP / El-Torito image compliant with MBR/GPT standards,
|
||||||
|
# we first append it as a partition and then point the El Torito at
|
||||||
|
# it. See https://lists.debian.org/debian-cd/2019/07/msg00007.html
|
||||||
|
opts: list[str | pathlib.Path] = [
|
||||||
|
"-append_partition",
|
||||||
|
"2",
|
||||||
|
"0xef",
|
||||||
|
self.scratch.joinpath("cd-boot-efi.img"),
|
||||||
|
"-appended_part_as_gpt",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Some BIOSes ignore removable disks with no partitions marked bootable
|
||||||
|
# in the MBR. Make sure our protective MBR partition is marked bootable.
|
||||||
|
opts.append("--mbr-force-bootable")
|
||||||
|
|
||||||
|
# Start a new entry in the el torito boot catalog
|
||||||
|
opts.append("-eltorito-alt-boot")
|
||||||
|
|
||||||
|
# Specify where the el torito UEFI boot image "name". We use a special
|
||||||
|
# syntax available in latest xorriso to point at our newly-created
|
||||||
|
# partition.
|
||||||
|
opts.extend(["-e", "--interval:appended_partition_2:all::"])
|
||||||
|
|
||||||
|
# Whether to emulate a floppy or not is a per-boot-catalog-entry
|
||||||
|
# thing, so we need to say it again.
|
||||||
|
opts.append("-no-emul-boot")
|
||||||
|
|
||||||
|
# Create a partition table entry that covers the iso9660 filesystem
|
||||||
|
opts.extend(["-partition_offset", "16"])
|
||||||
|
|
||||||
|
return opts
|
||||||
Loading…
x
Reference in New Issue
Block a user