#!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0-only set -e args=() declare -i package=0 extramodules=0 install_preset() { local pkgbase="$1" preset="$2" if [[ ! -e "$preset" ]]; then if [[ -e "$preset.pacsave" ]]; then # move the pacsave to the template mv -- "${preset}.pacsave" "$preset" else # create the preset from the template sed "s|%PKGBASE%|${pkgbase}|g" /usr/share/mkinitcpio/hook.preset \ | install -Dm644 /dev/stdin "$preset" fi fi } generate_presets() { local preset pkgbase pkgbase_path # only make presets for kernels with a pkgbase for pkgbase_path in /usr/lib/modules/*/pkgbase; do if read -r pkgbase &>/dev/null <"$pkgbase_path"; then preset="/etc/mkinitcpio.d/${pkgbase}.preset" install_preset "$pkgbase" "$preset" fi done } remove_preset() { if [[ -n "$pkgbase" && -e "$preset" ]]; then if ! cmp "$preset" <(sed "s|%PKGBASE%|${pkgbase}|g" /usr/share/mkinitcpio/hook.preset) &>/dev/null; then if [[ ! -e "$preset.pacsave" ]]; then # save the preset as pacsave mv -- "$preset" "$preset.pacsave" && return 0 fi else # remove the preset rm -- "$preset" && return 0 fi fi } # is_kernelcopy checks whether a `*_kver` value from a .preset file is a # filename that the .preset expects us to copy the kernel file to. is_kernelcopy() { local preset_kver="$1" # Check that this is a filepath (e.g. "/boot/vmlinuz-${pkgbase}") rather # than a version number (e.g. "6.5.2-arch-1"). # # mkinitcpio:resolve_kernver() does this by checking whether the string # starts with a "/", so do the same check here. if [[ "${preset_kver}" != /* ]]; then return 1 fi # Check that this isn't the filepath of the original # "/usr/lib/modules/${kver}-${pkgbase}/vmlinuz" file that we'd be copying # from... or really any other file that is actually owned by a package. if pacman -Qoq -- "${preset_kver}" &>/dev/null; then return 1 fi return 0 } read_preset() { local pkgbase="$1" p preset_image preset_uki preset_kver local unsorted_filelist=() unsorted_kernellist=() if [[ -v PRESETS ]]; then for p in "${PRESETS[@]}"; do declare -n preset_image="${p}_image" preset_uki="${p}_uki" preset_kver="${p}_kver" if [[ -v preset_image ]]; then unsorted_filelist+=("${preset_image}") elif [[ -v ALL_image ]]; then unsorted_filelist+=("${ALL_image}") fi if [[ -v preset_uki ]]; then unsorted_filelist+=("${preset_uki}") elif [[ -v ALL_uki ]]; then unsorted_filelist+=("${ALL_uki}") fi if [[ -v preset_kver ]]; then if is_kernelcopy "${preset_kver}"; then unsorted_filelist+=("${preset_kver}") fi unsorted_kernellist+=("${preset_kver}") elif [[ -v ALL_kver ]]; then if is_kernelcopy "${ALL_kver}"; then unsorted_filelist+=("${ALL_kver}") fi unsorted_kernellist+=("${ALL_kver}") fi done else unsorted_filelist+=("/boot/vmlinuz-${pkgbase}" "/boot/initramfs-${pkgbase}.img" "/boot/initramfs-${pkgbase}-fallback.img" "/efi/EFI/Linux/arch-${pkgbase}.efi" "/efi/EFI/Linux/arch-${pkgbase}-fallback.efi") unsorted_kernellist+=("/boot/vmlinuz-${pkgbase}") fi # Deduplicate file lists filelist=() while IFS='' read -r -d '' arrayelement; do filelist+=("$arrayelement") done < <(printf '%s\0' "${unsorted_filelist[@]}" | LC_ALL=C.UTF-8 sort -uz) kernellist=() while IFS='' read -r -d '' arrayelement; do kernellist+=("$arrayelement") done < <(printf '%s\0' "${unsorted_kernellist[@]}" | LC_ALL=C.UTF-8 sort -uz) } install_kernel() { local pkgbase="$1" local kernel preset="/etc/mkinitcpio.d/${pkgbase}.preset" install_preset "$pkgbase" "$preset" ( # source the preset to get the kernel and image locations # shellcheck disable=SC1090 [[ -s "$preset" ]] && . "$preset" read_preset "$pkgbase" # always install the kernel for kernel in "${kernellist[@]}"; do if is_kernelcopy "${kernel}"; then install -Dm644 -- "${line}" "${kernel}" fi done ) add_pkgbase_to_args "$pkgbase" } remove_kernel() { local pkgbase="$1" local preset="/etc/mkinitcpio.d/${pkgbase}.preset" # subshell to avoid namespace pollution ( # source the preset to get the kernel and image locations # shellcheck disable=SC1090 [[ -s "$preset" ]] && . "$preset" read_preset "$pkgbase" # access all the files to trigger any potential automounts stat -- /boot/ /efi/ "${filelist[@]}" &>/dev/null || : # remove the actual kernel and images for the package being removed rm -f -- "${filelist[@]}" ) || return # remove the preset remove_preset "$pkgbase" "$preset" } add_pkgbase_to_args() { local pkgbase="$1" # do not add the same pkgbase twice # shellcheck disable=SC2076 if [[ " ${args[*]} " =~ " -p ${pkgbase} " ]]; then return 0 fi # compound args for each kernel args+=(-p "$pkgbase") } while read -r line; do if [[ "${line%%-git}" == "mkinitcpio" && "$1" == "install" ]]; then # generate presets for each kernel on package install generate_presets package=1 continue fi if [[ "$line" != */vmlinuz && "$line" != 'usr/lib/modules/'*'/extramodules/' ]]; then # triggers when it's a change other than to the kernel and its modules package=1 continue fi if [[ "$line" == 'usr/lib/modules/'*'/extramodules/' ]]; then line="${line%/extramodules/}" extramodules=1 else extramodules=0 fi if ! read -r pkgbase &>/dev/null <"${line%/vmlinuz}/pkgbase"; then # if the kernel has no pkgbase, we skip it continue fi case "$1" in install) if (( extramodules )); then add_pkgbase_to_args "$pkgbase" continue fi install_kernel "$pkgbase" ;; remove) remove_kernel "$pkgbase" ;; esac done if (( package )) && compgen -G /etc/mkinitcpio.d/"*.preset" > /dev/null; then case "$1" in install) # change to use all presets args=(-P) ;; remove) shopt -s nullglob for preset in /etc/mkinitcpio.d/*.preset; do pkgbase=${preset##*/} pkgbase=${pkgbase%.preset} remove_preset "$pkgbase" "$preset" done shopt -u nullglob ;; esac fi if [[ "$1" == "install" ]] && (( ${#args[@]} )); then mkinitcpio "${args[@]}" fi