Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mkinitcpio] Create UEFI executables #53

Merged
merged 12 commits into from
Jul 7, 2021
47 changes: 46 additions & 1 deletion man/mkinitcpio.8.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Options
comma-separated. This option can be specified multiple times.

*-c, \--config* 'config'::
Use 'config' file to generate the ramdisk. Default: /etc/mkinitcpio.conf
Use 'config' file to generate the ramdisk. Default: '/etc/mkinitcpio.conf'.

*-d, \--generatedir* 'directory'::
Set 'directory' as the location where the initramfs is built. This might be
Expand Down Expand Up @@ -67,6 +67,14 @@ Options
*-n, \--nocolor*::
Disable color output.

*-U, \--uefi* 'filename'::
Generate a UEFI executable as 'filename'. If a CPIO image is successfully
built, it will be used to generate a UEFI executable stub image for UEFI
booting. This combines the initramfs, the kernel, any specified microcode
and the kernel cmdline into one executable. This is useful for boot chain
integrity where the file is signed. Default: no.
For a list of relevant options see 'Options for UEFi executable' below.

*-P, \--allpresets*::
Process all presets contained in '/etc/mkinitcpio.d'. See the '-p' option for
more detail about presets.
Expand Down Expand Up @@ -103,6 +111,34 @@ Options
*-z, \--compress* 'compress'::
Override the compression method with the 'compress' program.

Options for UEFi executable
---------------------------

*--cmdline* 'config'::
Use kernel cmdline with UEFI executable. If none is specified it will
try find one of the files '/etc/kernel/cmdline', '/usr/share/kernel/cmdline'
or '/proc/cmdline'.

*--splash* 'filename'::
UEFI executables can show a bitmap file on boot.

*--uefistub* 'filename'::
UEFI stub image used for UEFI executable generation.
Default: Attempts to look for a systemd-boot or gummiboot
stub loader.

*--kernelimage* 'filename'::
Include a kernel image for the UEFI executable. Default: one of
'/lib/modules/$KERNELVERSION/vmlinuz', '/boot/vmlinuz-$KERNELVERSION', or
'/boot/vmlinuz-linux'.

*--microcode* 'filename'::
Include microcode into the UEFI executable. Default: no.

*--osrelease* 'filename'::
Include a os-release file for the UEFI executable.
Default: '/etc/os-release' or '/usr/lib/os-release'.

About Presets
-------------
A preset is a pre-defined definition on how to create an initial ramdisk.
Expand Down Expand Up @@ -349,6 +385,15 @@ Examples
Create an initial ramdisk for the kernel at /boot/vmlinuz-linux. The
resulting image will be written to /boot/initramfs-linux.img.

*mkinitcpio -U /efi/EFI/Linux/systemd-linux.efi*::
Create an initial ramdisk for the kernel along with a UEFI executable.
The resuling executable will be written to /efi/EFI/Linux/systemd-linux.efi.

*mkinitcpio -U /efi/EFI/Linux/systemd-linux.efi --microcode /boot/intel-ucode.img --splash /usr/share/systemd/bootctl/splash-arch.bmp*::
Create an initial ramdisk for the kernel and an UEFI executable. This
also includes the Intel CPU microcode and a splash image which will be
used during boot.

See also
--------
A more thorough article on configuring mkinitcpio:
Expand Down
160 changes: 157 additions & 3 deletions mkinitcpio
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ _d_presets=mkinitcpio.d
# options and runtime data
_optmoduleroot= _optgenimg=
_optcompress= _opttargetdir=
_optosrelease=
_optuefi= _optmicrocode=() _optcmdline= _optsplash= _optkernelimage= _optuefistub=
_optshowautomods=0 _optsavetree=0 _optshowmods=0
_optquiet=1 _optcolor=1
_optskiphooks=() _optaddhooks=() _hooks=() _optpreset=()
Expand Down Expand Up @@ -58,11 +60,20 @@ usage: ${0##*/} [options]
-s, --save Save build directory. (default: no)
-d, --generatedir <dir> Write generated image into <dir>
-t, --builddir <dir> Use DIR as the temporary build directory
-D, --hookdir <dir> Specify where to look for hooks.
-D, --hookdir <dir> Specify where to look for hooks
-U, --uefi <path> Build an UEFI executable
-V, --version Display version information and exit
-v, --verbose Verbose output (default: no)
-z, --compress <program> Use an alternate compressor on the image

Options for UEFI executable (-U, --uefi):
--cmdline <cmdline> Set kernel line (default content: /etc/kernel/cmdline, /proc/cmdline)
--microcode <path> Location of microcode
--osrelease <path> Include os-release (default: /etc/os-release)
--splash <path> Include bitmap splash
--kernelimage <path> Kernel image
--uefistub <path> Location of UEFI stub loader

EOF
}

Expand Down Expand Up @@ -251,6 +262,92 @@ build_image() {
fi
}

build_uefi(){
local out=$1 initramfs=$2 cmdline=$3 osrelease=$4 splash=$5 kernelimg=$6 uefistub=$7 microcode=(${@:7}) errmsg=
OBJCOPYARGS=()

msg "Creating UEFI executable: %s" "$out"

if [[ -z "$uefistub" ]]; then
for stub in {/usr,}/lib/{systemd/boot/efi,gummiboot}/linux{x64,ia32}.efi.stub; do
Foxboron marked this conversation as resolved.
Show resolved Hide resolved
if [[ -f "$stub" ]]; then
uefistub="$stub"
msg2 "Using UEFI stub: %s" "$uefistub"
break
fi
done
elif [[ ! -f "$uefisub" ]]; then
error "UEFI stub '%s' not found" "$uefistub"
return 1
fi

if [[ -z "$kernelimg" ]]; then
for img in "/lib/modules/$KERNELVERSION/vmlinuz" "/boot/vmlinuz-$KERNELVERSION" "/boot/vmlinuz-linux"; do
if [[ -f "$img" ]]; then
kernelimg="$img"
msg2 "Using kernel image: %s" "$kernelimg"
break
fi
done
fi
if [[ ! -f "$kernelimg" ]]; then
error "Kernel image '%s' not found" "$kernelimage"
return 1
fi

if [[ -z "$cmdline" ]]; then
if [[ -f "/etc/kernel/cmdline" ]]; then
cmdline="/etc/kernel/cmdline"
elif [[ -f "/usr/lib/kernel/cmdline" ]]; then
cmdline="/usr/lib/kernel/cmdline"
else
warning "Note: /etc/kernel/cmdline does not exist and --cmdline is unset!"
cmdline="/proc/cmdline"
Foxboron marked this conversation as resolved.
Show resolved Hide resolved
warning "Reusing current kernel cmdline from $cmdline"
fi
msg2 "Using cmdline file %s" "$cmdline"
fi
if [[ ! -f "$cmdline" ]]; then
Foxboron marked this conversation as resolved.
Show resolved Hide resolved
error "Kernel cmdline file '%s' not found" "$cmdline"
return 1
fi

if [[ -z "$osrelease" ]]; then
if [[ -f "/etc/os-release" ]]; then
osrelease="/etc/os-release"
elif [[ -f "/usr/lib/os-release" ]]; then
osrelease="/usr/lib/os-release"
fi
msg2 "Using os-release file %s" "$osrelease"
fi
if [[ ! -f "$osrelease" ]]; then
error "os-release file '%s' not found" "$osrelease"
return 1
fi

if [[ -z "$initramfs" ]]; then
error "Initramfs '%s' not found" "$initramfs"
return 1
fi

if [[ -n "$splash" ]]; then
OBJCOPYARGS+=(--add-section .splash="$splash" --change-section-vma .splash=0x40000)
fi
objcopy \
--add-section .osrel="$osrelease" --change-section-vma .osrel=0x20000 \
--add-section .cmdline=<(grep '^[^#]' "$cmdline" | tr -s '\n' ' ') --change-section-vma .cmdline=0x30000 \
--add-section .linux="$kernelimg" --change-section-vma .linux=0x2000000 \
--add-section .initrd=<(cat ${microcode[@]} "$initramfs") --change-section-vma .initrd=0x3000000 \
${OBJCOPYARGS[@]} "$uefistub" "$out"
Foxboron marked this conversation as resolved.
Show resolved Hide resolved

status=$?
if (( $status )) ; then
error "UEFI executable generation FAILED"
else
msg "UEFI executable generation successful"
fi
}

process_preset() (
local preset=$1 preset_image= preset_options=
local -a preset_mkopts preset_cmd
Expand Down Expand Up @@ -307,6 +404,17 @@ process_preset() (
preset_cmd+=(${!preset_options}) # intentional word splitting
fi

preset_efi_image=${p}_efi_image
if [[ ${!preset_efi_image:-$ALL_efi_image} ]]; then
preset_cmd+=(-U "${!preset_efi_image:-$ALL_efi_image}")
fi

preset_microcode=${p}_microcode[@]
if [[ ${!preset_microcode:-$ALL_microcode} ]]; then
for mc in "${!preset_microcode:-${ALL_microcode[@]}}"; do
preset_cmd+=(-m "$mc")
done
fi
msg2 "${preset_cmd[*]}"
MKINITCPIO_PROCESS_PRESET=1 "$0" "${preset_cmd[@]}"
(( $? )) && ret=1
Expand Down Expand Up @@ -345,10 +453,11 @@ preload_builtin_modules() {
trap 'cleanup 130' INT
trap 'cleanup 143' TERM

_opt_short='A:c:D:g:H:hk:nLMPp:r:S:sd:t:Vvz:'
_opt_short='A:c:D:g:H:hk:nLMPp:r:S:sd:t:U:Vvz:'
_opt_long=('add:' 'addhooks:' 'config:' 'generate:' 'hookdir': 'hookhelp:' 'help'
'kernel:' 'listhooks' 'automods' 'moduleroot:' 'nocolor' 'allpresets'
'preset:' 'skiphooks:' 'save' 'generatedir:' 'builddir:' 'version' 'verbose' 'compress:')
'preset:' 'skiphooks:' 'save' 'generatedir:' 'builddir:' 'version' 'verbose' 'compress:'
'uefi:' 'microcode:' 'splash:' 'kernelimage:' 'uefistub:')
Foxboron marked this conversation as resolved.
Show resolved Hide resolved

parseopts "$_opt_short" "${_opt_long[@]}" -- "$@" || exit 1
set -- "${OPTRET[@]}"
Expand All @@ -367,6 +476,10 @@ while :; do
shift
_f_config=$1
;;
--cmdline)
shift
_optcmdline=$1
;;
-k|--kernel)
shift
KERNELVERSION=$1
Expand Down Expand Up @@ -400,6 +513,13 @@ while :; do
-n|--nocolor)
_optcolor=0
;;
-U|--uefi)
shift
[[ -d $1 ]] && die "Invalid image path -- must not be a directory"
if ! _optuefi=$(readlink -f "$1") || [[ ! -e ${_optuefi%/*} ]]; then
die "Unable to write to path: \`%s'" "$1"
Foxboron marked this conversation as resolved.
Show resolved Hide resolved
fi
;;
-v|--verbose)
_optquiet=0
;;
Expand All @@ -418,13 +538,35 @@ while :; do
hook_list
exit 0
;;
--splash)
shift
[[ -f $1 ]] || die "Invalid file -- must be a file"
_optsplash=$1
;;
--kernelimage)
shift
_optkernelimage=$1
;;
--uefistub)
shift
_optkernelimage=$1
;;
-M|--automods)
_optshowautomods=1
;;
--microcode)
shift
_optmicrocode+=($1)
;;
-P|--allpresets)
_optpreset=("$_d_presets"/*.preset)
[[ -e ${_optpreset[0]} ]] || die "No presets found in $_d_presets"
;;
--osrelease)
shift
[[ ! -f $1 ]] && die "Invalid file -- must be a file"
_optosrelease=$1
;;
-t|--builddir)
shift
export TMPDIR=$1
Expand Down Expand Up @@ -459,6 +601,14 @@ if [[ -n $_d_flag_hooks && -n $_d_flag_install ]]; then
_d_install=${_d_flag_install%:}
fi


# If we specified --uefi but no -g we want to create a temporary initramfs which will be used with the efi executable.
if [[ $_optuefi && $_optgenimg == "" ]]; then
tmpfile=$(mktemp -t mkinitcpio.XXXXXX)
trap "rm $tmpfile" EXIT
_optgenimg="$tmpfile"
fi

# insist that /proc and /dev be mounted (important for chroots)
# NOTE: avoid using mountpoint for this -- look for the paths that we actually
# use in mkinitcpio. Avoids issues like FS#26344.
Expand Down Expand Up @@ -556,6 +706,10 @@ else
msg "Dry run complete, use -g IMAGE to generate a real image"
fi

if [[ $_optuefi && $_optgenimg ]]; then
build_uefi "$_optuefi" "$_optgenimg" "$_optcmdline" "$_optosrelease" "$_optsplash" "$_optkernelimage" "$_optuefistub" "${_optmicrocode[@]}"
fi

cleanup $(( !!_builderrors ))

# vim: set ft=sh ts=4 sw=4 et:
3 changes: 3 additions & 0 deletions mkinitcpio.d/example.preset
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ PRESETS=('default' 'fallback')
# as the path to an kernel image.
ALL_kver='/boot/vmlinuz-linux'
ALL_config='/etc/mkinitcpio.conf'
ALL_microcode=(/boot/*-ucode.img)

# presetname_kver - the kernel version (omit if ALL_kver should be used)
# presetname_config - the configuration file (omit if ALL_config should be used)
Expand All @@ -17,9 +18,11 @@ ALL_config='/etc/mkinitcpio.conf'
#default_kver="3.0-ARCH"
#default_config="/etc/mkinitcpio.conf"
default_image="/tmp/initramfs-linux.img"
default_efi_image="/efi/EFI/Linux/arch-linux.efi"
default_options=""

#fallback_kver="3.0-ARCH"
#fallback_config="/etc/mkinitcpio.conf"
fallback_image="/tmp/initramfs-linux-fallback.img"
fallback_efi_image="/efi/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"