Files
systemd/src/shared/reboot-util.h
T
Daan De Meyer e107c7ead0 systemctl: replace kexec-tools dependency with direct kexec_file_load() syscall
Replace the fork+exec of /usr/bin/kexec in load_kexec_kernel() with a
direct kexec_file_load() syscall, removing the runtime dependency on
kexec-tools for systemctl kexec.

The kexec_file_load() syscall (available since Linux 3.17) accepts
kernel and initrd file descriptors directly, letting the kernel handle
image parsing, segment setup, and purgatory internally. This is much
simpler than the older kexec_load() syscall which requires complex
userspace setup of memory segments and boot protocol structures — that
complexity is the raison d'être of kexec-tools.

The implementation follows the established libc wrapper pattern: a
missing_kexec_file_load() fallback in src/libc/kexec.c calls the
syscall directly when glibc doesn't provide a wrapper (which is
currently always the case). The syscall is not available on all
architectures — alpha, i386, ia64, m68k, mips, sh, and sparc lack
__NR_kexec_file_load — so the wrapper and caller are guarded with
HAVE_KEXEC_FILE_LOAD_SYSCALL to compile cleanly everywhere.

When kexec_file_load() rejects the kernel image with ENOEXEC (e.g. the
image is compressed or wrapped in a PE container that the kernel's kexec
handler doesn't understand natively), we attempt to unwrap/decompress
and retry. This is effectively the same decompression and extraction
logic that already lives in src/ukify/ukify.py (maybe_decompress() and
get_zboot_kernel()), now implemented in C so that systemctl can handle
it natively without shelling out to external tools:

 - Compressed kernels (Image.gz, Image.zst, Image.xz, Image.lz4): the
   format is detected by magic bytes (per RFC 1952, RFC 8878,
   tukaani.org xz spec, and lz4 frame format spec) and decompressed to
   a memfd using the existing decompress_stream_*() infrastructure plus
   the new gzip support from the previous commit. This is primarily
   needed on arm64 where kexec_file_load() only accepts raw Image files.
   On x86_64, bzImage is already the native format and works directly.

 - EFI ZBOOT PE images (vmlinuz.efi): detected by "MZ" + "zimg" magic
   at the start of the file. The compressed payload offset, size, and
   compression type are read from the ZBOOT header defined in
   linux/drivers/firmware/efi/libstub/zboot-header.S.

 - Unified Kernel Images (UKI): detected as PE files with a .linux
   section via the existing pe_is_uki() infrastructure. The .linux
   section (kernel) and optionally .initrd section are extracted to
   memfds. When a UKI provides an embedded initrd and the boot entry
   doesn't specify one separately, the embedded initrd is used.

The try-first-then-decompress approach means we never decompress
unnecessarily: on x86_64 the first kexec_file_load() call succeeds
immediately with the raw bzImage, and on architectures where the
kernel's kexec handler natively understands PE (like LoongArch with
kexec_efi_ops), ZBOOT/UKI images work without decompression too.

If kexec_file_load() is unavailable (architectures without the syscall)
or all attempts fail, we fall back to forking+execing the kexec binary.
This preserves compatibility on architectures like i386 and mips where
only the older kexec_load() syscall exists and kexec-tools is needed to
handle the complex userspace setup.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 11:13:04 +02:00

34 lines
1.3 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <linux/reboot.h> /* IWYU pragma: export */
#include <sys/reboot.h> /* IWYU pragma: export */
#include "shared-forward.h"
/* glibc defines the reboot() API call, which is a wrapper around the system call of the same name, but
* without the extra "arg" parameter. Since we need that parameter for some calls, let's add a "raw" wrapper
* that is defined the same way, except it takes the additional argument. */
int raw_reboot(int cmd, const void *arg);
bool reboot_parameter_is_valid(const char *parameter);
int update_reboot_parameter_and_warn(const char *parameter, bool keep);
typedef enum RebootFlags {
REBOOT_LOG = 1 << 0, /* log about what we are going to do and all errors */
REBOOT_DRY_RUN = 1 << 1, /* return 0 right before actually doing the reboot */
REBOOT_FALLBACK = 1 << 2, /* fall back to plain reboot() if argument-based reboot doesn't work, isn't configured or doesn't apply otherwise */
} RebootFlags;
int read_reboot_parameter(char **parameter);
int reboot_with_parameter(RebootFlags flags);
bool shall_restore_state(void);
bool kexec_loaded(void);
int kexec(void);
int kexec_maybe_decompress_kernel(const char *path, int fd, int *ret_kernel_fd, int *ret_initrd_fd);
int create_shutdown_run_nologin_or_warn(void);