Skip to content

Commit

Permalink
Regenerate unified kernel images when installing/updating kernels in
Browse files Browse the repository at this point in the history
image.
  • Loading branch information
DaanDeMeyer committed Aug 4, 2020
1 parent aedb770 commit 760796b
Showing 1 changed file with 110 additions and 103 deletions.
213 changes: 110 additions & 103 deletions mkosi
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,72 @@ MKOSI_COMMANDS_SUDO = ("build", "clean") + MKOSI_COMMANDS_CMDLINE
MKOSI_COMMANDS = ("build", "clean", "help", "summary") + MKOSI_COMMANDS_CMDLINE


DRACUT_UNIFIED_KERNEL_INSTALL = """\
#!/bin/bash -e
COMMAND="$1"
KERNEL_VERSION="$2"
BOOT_DIR_ABS="$3"
KERNEL_IMAGE="$4"
ROOTHASH="${5:-}"
# If KERNEL_INSTALL_MACHINE_ID is defined but empty, BOOT_DIR_ABS is a fake directory so let's skip creating
# the unified kernel image.
if [ -z "${KERNEL_INSTALL_MACHINE_ID-unset}" ]; then
exit 0
fi
# Strip machine ID and kernel version to get the boot directory.
PREFIX=$(dirname $(dirname "$BOOT_DIR_ABS"))
BOOT_BINARY="${PREFIX}/EFI/Linux/linux-${KERNEL_VERSION}.efi"
case "$COMMAND" in
add)
if [[ -f /etc/kernel/cmdline ]]; then
read -r -d '' BOOT_OPTIONS < /etc/kernel/cmdline || true
elif [[ -f /usr/lib/kernel/cmdline ]]; then
read -r -d '' BOOT_OPTIONS < /usr/lib/kernel/cmdline || true
else
read -r -d '' BOOT_OPTIONS < /proc/cmdline || true
fi
if [[ -n "$ROOTHASH" ]]; then
BOOT_OPTIONS="${BOOT_OPTIONS} roothash=$ROOTHASH"
fi
if [[ -n "$KERNEL_IMAGE" ]]; then
DRACUT_KERNEL_IMAGE_OPTION="--kernel-image ${KERNEL_IMAGE}"
else
DRACUT_KERNEL_IMAGE_OPTION=""
fi
dracut \\
--quiet \\
--force \\
--no-hostonly \\
--uefi \\
--uefi-stub "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" \\
--kver "$KERNEL_VERSION" \\
$DRACUT_KERNEL_IMAGE_OPTION \\
--kernel-cmdline "$BOOT_OPTIONS" \\
--include /usr/lib/systemd/system/systemd-volatile-root.service /usr/lib/systemd/system/systemd-volatile-root.service \\
--include /usr/lib/systemd/systemd-volatile-root /usr/lib/systemd/systemd-volatile-root \\
--include /usr/lib/systemd/systemd-veritysetup /usr/lib/systemd/systemd-veritysetup \\
--include /usr/lib/systemd/system-generators/systemd-veritysetup-generator /usr/lib/systemd/system-generators/systemd-veritysetup-generator \\
--include /usr/bin/systemd-repart /usr/bin/systemd-repart \\
--include /usr/lib/systemd/system/systemd-repart.service /usr/lib/systemd/system/systemd-repart.service \\
--include /usr/lib/systemd/system/initrd-root-fs.target.wants/systemd-repart.service /usr/lib/systemd/system/initrd-root-fs.target.wants/systemd-repart.service \\
--include /usr/lib/systemd/system-generators/systemd-debug-generator /usr/lib/systemd/system-generators/systemd-debug-generator \\
--add qemu \\
"$BOOT_BINARY"
;;
remove)
rm -f -- "$BOOT_BINARY"
;;
esac
"""


# This global should be initialized after parsing arguments
arg_debug = ()

Expand Down Expand Up @@ -1428,40 +1494,36 @@ def check_if_url_exists(url: str) -> bool:
return False


def disable_kernel_install(args: CommandLineArguments, root: str) -> List[str]:
# Let's disable the automatic kernel installation done by the
# kernel RPMs. After all, we want to built our own unified kernels
# that include the root hash in the kernel command line and can be
# signed as a single EFI executable. Since the root hash is only
# known when the root file system is finalized we turn off any
# kernel installation beforehand.
def disable_kernel_install(args: CommandLineArguments, root: str) -> None:
# Let's disable the automatic kernel installation done by the kernel RPMs. After all, we want to built
# our own unified kernels that include the root hash in the kernel command line and can be signed as a
# single EFI executable. Since the root hash is only known when the root file system is finalized we turn
# off any kernel installation beforehand.
#
# For BIOS mode, we don't have that option, so do not mask the units
# For BIOS mode, we don't have that option, so do not mask the units.
if not args.bootable or args.bios_partno is not None:
return []
return

for d in ("etc", "etc/kernel", "etc/kernel/install.d"):
mkdir_last(os.path.join(root, d), 0o755)

masked: List[str] = []

for f in ("50-dracut.install", "51-dracut-rescue.install", "90-loaderentry.install"):
path = os.path.join(root, "etc/kernel/install.d", f)
os.symlink("/dev/null", path)
masked += [path]

return masked


def reenable_kernel_install(args: CommandLineArguments, root: str, masked: List[str]) -> None:
# Undo disable_kernel_install() so the final image can be used
# with scripts installing a kernel following the Bootloader Spec
def reenable_kernel_install(args: CommandLineArguments, root: str) -> None:
# Reenable 50-dracut-unified-kernel.install so we can run kernel-install to install unified kernel images.

if not args.bootable:
if not args.bootable or args.bios_partno is not None:
return

for f in masked:
os.unlink(f)
hook_path = os.path.join(root, "etc/kernel/install.d/50-dracut-unified-kernel.install")
with open(hook_path, "w") as f:
f.write(DRACUT_UNIFIED_KERNEL_INSTALL)

st = os.stat(hook_path)
os.chmod(hook_path, st.st_mode | stat.S_IEXEC)


def make_rpm_list(args: argparse.Namespace, packages: List[str]) -> List[str]:
Expand Down Expand Up @@ -1661,8 +1723,6 @@ def setup_dnf(args: CommandLineArguments, root: str, repos: List[Repo] = []) ->

@completestep('Installing Photon')
def install_photon(args: CommandLineArguments, root: str, do_run_build_script: bool) -> None:
masked = disable_kernel_install(args, root)

release_url = "baseurl=https://dl.bintray.com/vmware/photon_release_$releasever_$basearch"
updates_url = "baseurl=https://dl.bintray.com/vmware/photon_updates_$releasever_$basearch"
gpgpath='/etc/pki/rpm-gpg/VMWARE-RPM-GPG-KEY'
Expand All @@ -1677,7 +1737,6 @@ def install_photon(args: CommandLineArguments, root: str, do_run_build_script: b
packages += ["linux", "initramfs"]

invoke_tdnf(args, root, args.repositories or ["photon", "photon-updates"], packages)
reenable_kernel_install(args, root, masked)


@completestep('Installing Clear Linux')
Expand Down Expand Up @@ -1746,8 +1805,6 @@ def install_fedora(args: CommandLineArguments, root: str, do_run_build_script: b
else:
args.releasever = args.release

masked = disable_kernel_install(args, root)

arch = args.architecture or platform.machine()

if args.mirror:
Expand Down Expand Up @@ -1782,13 +1839,9 @@ def install_fedora(args: CommandLineArguments, root: str, do_run_build_script: b
with open(os.path.join(root, 'etc/locale.conf'), 'w') as f:
f.write('LANG=C.UTF-8\n')

reenable_kernel_install(args, root, masked)


@completestep('Installing Mageia')
def install_mageia(args: CommandLineArguments, root: str, do_run_build_script: bool) -> None:
masked = disable_kernel_install(args, root)

if args.mirror:
baseurl = f"{args.mirror}/distrib/{args.release}/x86_64/media/core/"
release_url = f"baseurl={baseurl}/release/"
Expand All @@ -1813,13 +1866,10 @@ def install_mageia(args: CommandLineArguments, root: str, do_run_build_script: b
packages += args.build_packages or []
invoke_dnf(args, root, args.repositories or ["mageia", "updates"], packages)

reenable_kernel_install(args, root, masked)


@completestep('Installing OpenMandriva')
def install_openmandriva(args: CommandLineArguments, root: str, do_run_build_script: bool) -> None:
release = args.release.strip("'")
masked = disable_kernel_install(args, root)

if release[0].isdigit():
release_model = "rock"
Expand Down Expand Up @@ -1852,8 +1902,6 @@ def install_openmandriva(args: CommandLineArguments, root: str, do_run_build_scr
packages += args.build_packages or []
invoke_dnf(args, root, args.repositories or ["openmandriva", "updates"], packages)

reenable_kernel_install(args, root, masked)


def invoke_yum(args: CommandLineArguments,
root: str,
Expand Down Expand Up @@ -1960,8 +2008,6 @@ def install_centos_new(args: CommandLineArguments, root: str, epel_release: str)

@completestep('Installing CentOS')
def install_centos(args: CommandLineArguments, root: str, do_run_build_script: bool) -> None:
masked = disable_kernel_install(args, root)

epel_release = args.release.split('.')[0]

# Centos Repositories were changed between 7 and 8
Expand Down Expand Up @@ -1994,7 +2040,6 @@ def install_centos(args: CommandLineArguments, root: str, do_run_build_script: b
packages += args.build_packages or []

invoke_dnf_or_yum(args, root, repos, packages)
reenable_kernel_install(args, root, masked)


def debootstrap_knows_arg(arg: str) -> bool:
Expand Down Expand Up @@ -2474,7 +2519,6 @@ def reset_machine_id(args: CommandLineArguments, root: str, do_run_build_script:
os.unlink(machine_id)
except FileNotFoundError:
pass
open(machine_id, "w+b").close()
dbus_machine_id = os.path.join(root, 'var/lib/dbus/machine-id')
try:
os.unlink(dbus_machine_id)
Expand Down Expand Up @@ -2646,12 +2690,6 @@ def install_boot_loader_arch(args: CommandLineArguments, root: str, loopdev: str


def install_boot_loader_debian(args: CommandLineArguments, root: str, loopdev: str) -> None:
if "uefi" in args.boot_protocols:
kernel_version = next(filter(lambda x: x[0].isdigit(), os.listdir(os.path.join(root, "lib/modules"))))

run_workspace_command(args, root,
"/usr/bin/kernel-install", "add", kernel_version, "/boot/vmlinuz-" + kernel_version)

if "bios" in args.boot_protocols:
grub = "grub" if args.distribution in (Distribution.ubuntu, Distribution.debian) else "grub2"
# TODO: Just use "grub" once https://github.com/systemd/systemd/pull/16645 is widely available.
Expand Down Expand Up @@ -3178,76 +3216,43 @@ def install_unified_kernel(args: CommandLineArguments,
do_run_build_script: bool,
for_cache: bool,
root_hash: Optional[str]) -> None:
# Iterates through all kernel versions included in the image and
# generates a combined kernel+initrd+cmdline+osrelease EFI file
# from it and places it in the /EFI/Linux directory of the
# ESP. sd-boot iterates through them and shows them in the
# menu. These "unified" single-file images have the benefit that
# they can be signed like normal EFI binaries, and can encode
# everything necessary to boot a specific root device, including
# the root hash.
# Iterates through all kernel versions included in the image and generates a combined
# kernel+initrd+cmdline+osrelease EFI file from it and places it in the /EFI/Linux directory of the ESP.
# sd-boot iterates through them and shows them in the menu. These "unified" single-file images have the
# benefit that they can be signed like normal EFI binaries, and can encode everything necessary to boot a
# specific root device, including the root hash.

if not args.bootable or args.esp_partno is None:
return
if for_cache:
return

# Don't bother running dracut if this is a development
# build. Strictly speaking it would probably be a good idea to run
# it, so that the development environment differs as little as
# possible from the final build, but then again the initrd should
# not be relevant for building, and dracut is simply very slow,
# hence let's avoid it invoking it needlessly, given that we never
# actually invoke the boot loader on the development image.
# Don't bother running dracut if this is a development build. Strictly speaking it would probably be a
# good idea to run it, so that the development environment differs as little as possible from the final
# build, but then again the initrd should not be relevant for building, and dracut is simply very slow,
# hence let's avoid it invoking it needlessly, given that we never actually invoke the boot loader on the
# development image.
if do_run_build_script:
return
if args.distribution not in (Distribution.fedora, Distribution.mageia, Distribution.ubuntu, Distribution.debian, Distribution.centos, Distribution.centos_epel):
return

with complete_step("Generating combined kernel + initrd boot file"):
with os.scandir(os.path.join(root, "usr/lib/modules")) as d:
for kver in d:
if not kver.is_dir():
continue

cmdline = ' '.join(args.kernel_command_line)
if root_hash is not None:
cmdline += " roothash=" + root_hash

for kver in os.scandir(os.path.join(root, "usr/lib/modules")):
if not kver.is_dir():
continue

# Place kernel in XBOOTLDR partition if it is turned on, otherwise in the ESP
prefix = "/efi" if args.xbootldr_size is None else "/boot"

boot_binary = prefix + "/EFI/Linux/linux-" + kver.name
if root_hash is not None:
boot_binary += "-" + root_hash
boot_binary += ".efi"

dracut = ["/usr/bin/dracut",
"-v",
"--no-hostonly",
"--uefi",
"--uefi-stub", "/usr/lib/systemd/boot/efi/linuxx64.efi.stub",
"--kver", kver.name,
"--kernel-cmdline", cmdline]

# Temporary fix until dracut includes these in the image anyway
dracut += ("-i",) + ("/usr/lib/systemd/system/systemd-volatile-root.service",)*2 + \
("-i",) + ("/usr/lib/systemd/systemd-volatile-root",)*2 + \
("-i",) + ("/usr/lib/systemd/systemd-veritysetup",)*2 + \
("-i",) + ("/usr/lib/systemd/system-generators/systemd-veritysetup-generator",)*2 + \
("-i",) + ("/usr/bin/systemd-repart",)*2 + \
("-i",) + ("/usr/lib/systemd/system/systemd-repart.service",)*2 + \
("-i",) + ("/usr/lib/systemd/system/initrd-root-fs.target.wants/systemd-repart.service",)*2 + \
("-i",) + ("/usr/lib/systemd/system-generators/systemd-debug-generator",)*2

if args.output_format.is_squashfs:
dracut += ['--add-drivers', 'squashfs']

dracut += ['--add', 'qemu']

dracut += [boot_binary]
prefix = "/boot" if args.xbootldr_partno is not None else "/efi"
# While the kernel version can generally be found as a directory under /usr/lib/modules, the
# kernel image files can be found either in /usr/lib/modules/<kernel-version>/vmlinuz or in
# /boot depending on the distro. By invoking the kernel-install script directly, we can pass
# the empty string as the kernel image which causes the script to not pass the --kernel-image
# option to dracut so it searches the image for us.
cmdline = ["/etc/kernel/install.d/50-dracut-unified-kernel.install", "add", kver.name,
f"{prefix}/{args.machine_id}/{kver}", ""]
if root_hash is not None:
cmdline.append(root_hash)

run_workspace_command(args, root, *dracut)
run_workspace_command(args, root, *cmdline)


def secure_boot_sign(args: CommandLineArguments, root: str, do_run_build_script: bool, for_cache: bool) -> None:
Expand Down Expand Up @@ -4869,7 +4874,9 @@ def build_image(args: CommandLineArguments,
with mount_cache(args, root):
cached = reuse_cache_tree(args, root, do_run_build_script, for_cache, cached)
install_skeleton_trees(args, root, for_cache)
disable_kernel_install(args, root)
install_distribution(args, root, do_run_build_script=do_run_build_script, cached=cached)
reenable_kernel_install(args, root)
install_etc_hostname(args, root)
install_boot_loader(args, root, loopdev, do_run_build_script, cached)
run_prepare_script(args, root, do_run_build_script, cached)
Expand Down Expand Up @@ -4898,7 +4905,7 @@ def build_image(args: CommandLineArguments,
# the verity data, and hence really shouldn't modify the
# image anymore.
with mount_image(args, root, loopdev,
None if args.generated_root() and for_cache else encrypted_root, encrypted_home, encrypted_srv, encrypted_var, encrypted_tmp, root_read_only=True):
None if args.generated_root() and for_cache else encrypted_root, encrypted_home, encrypted_srv, encrypted_var, encrypted_tmp):
install_unified_kernel(args, root, do_run_build_script, for_cache, root_hash)
secure_boot_sign(args, root, do_run_build_script, for_cache)

Expand Down

0 comments on commit 760796b

Please sign in to comment.