#!/bin/sh
#
# Copyright (C) 1995 - 1998, Ian A. Murdock <imurdock@debian.org>
# Copyright (C) 1998, 1999, Guy Maor
# Copyright (C) 2002, Matthew Wilcox
# Copyright (C) 2002, 2004, 2005, 2007, 2009  Clint Adams
# Copyright (C) 2009  Manoj Srivasta
# Copyright 2020-2024 Gentoo Authors
#
# Install the kernel on a Linux system.
#
# This script is called by the kernel's "make install" if it is installed as
# /sbin/installkernel. It is also called by kernel-install.eclass.

SYSTEMD_KERNEL_INSTALL="${SYSTEMD_KERNEL_INSTALL:=1}"

if [ "${SYSTEMD_KERNEL_INSTALL}" = "1" ] && command -v kernel-install >/dev/null; then
	# If the ${0} of kernel-install is installkernel it takes its arguments
	# in the same way we do here. We could use "exec -a ${0} .. ${@}" for this,
	# but the -a argument is not POSIX, only bash.
	# TODO: maybe revisit this if we ever bashify the script.
	if [ ${#} -le 3 ] || [ "${4}" = "/boot" ]; then
		# kernel-install does not support relocation (ignores $4, see manual)
		exec kernel-install add --verbose "${1}" "${2}"
	else
		echo "WARNING: A custom installation directory is specified as fourth argument."
		echo "WARNING: Systemd kernel-install does not support this. Falling back"
		echo "WARNING: to legacy installkernel. SYSTEMD_KERNEL_INSTALL is ignored."
	fi
fi

set -e

# Parse the command line options.  Of course, powerpc has to be all
# different, and passes in a fifth argument, just because it is
# "special". We ignore the fifth argument, and do not flag is as an
# error, which it would be for any arch apart from powerpc
if [ ${#} -eq 3 ] || [ ${#} -eq 4 ] || [ ${#} -eq 5 ]; then
	ver="${1}"
	img="${2}"
	map="${3}"
	if [ ${#} -ge 4 ] && [ -n "${4}" ]; then
		dir="${4}"
	else
		dir="/boot"
	fi
else
	echo "Usage: installkernel <version> <image> <System.map> <directory>"
	exit 1
fi

dropindirs_sort() {
    for d; do
        for i in "${d}/"*; do
            [ -e "${i}" ] && echo "${i##*/}"
        done
    done | sort -Vu | while read -r f; do
        for d; do
            if [ -e "${d}/${f}" ]; then
                [ -x "${d}/${f}" ] && echo "${d}/${f}"
                continue 2
            fi
        done
    done
}

# Create backups of older versions before installing
updatever() {
	oldsuffix="${3}.old"
	if [ "${3%.efi}" != "${3}" ]; then
		# Some UEFIs enforce .efi suffix, so if using efi files retain suffix
		oldsuffix="-old${3}"
	fi

	if [ -f "${dir}/${1}-${ver}${3}" ]; then
		# If they are hardlinked together, mv will fail.
		rm -f "${dir}/${1}-${ver}${oldsuffix}"
		mv "${dir}/${1}-${ver}${3}" "${dir}/${1}-${ver}${oldsuffix}"
	fi

	cat "${2}" >"${dir}/${1}-${ver}${3}"

	# Update static version-less symlink/copy
	if [ -f "${dir}/${1}${3}" ] || [ -L "${dir}/${1}${3}" ]; then
		# The presence of "${dir}/${1}${3}" is unusual in modern installations, and
		# the results are mostly unused.  So only recreate them if they
		# already existed.
		if [ -L "${dir}/${1}${3}" ]; then
			# If we were using links, continue to use links, updating if
			# we need to.
			if [ "$(readlink -f "${dir}/${1}${3}")" = "${dir}/${1}-${ver}${3}" ]; then
				# Yup, we need to change
				ln -sf "${1}-${ver}${oldsuffix}" "${dir}/${1}${oldsuffix}"
			else
				# If they are hardlinked together, mv will fail.
				rm -f "${dir}/${1}${oldsuffix}"
				mv "${dir}/${1}${3}" "${dir}/${1}${oldsuffix}"
			fi
			ln -sf "${1}-${ver}${3}" "${dir}/${1}${3}"
		else # No links
			# If they are hardlinked together, mv will fail.
			rm -f "${dir}/${1}${oldsuffix}"
			mv "${dir}/${1}${3}" "${dir}/${1}${oldsuffix}"
			cat "${2}" >"${dir}/${1}${3}"
		fi
	fi
}

if [ -n "${INSTALLKERNEL_CONF_ROOT}" ]; then
	install_conf="${INSTALLKERNEL_CONF_ROOT}/install.conf"
elif [ -f "/etc/kernel/install.conf" ]; then
	install_conf="/etc/kernel/install.conf"
elif [ -f "/usr/lib/kernel/install.conf" ]; then
	install_conf="/usr/lib/kernel/install.conf"
else
	install_conf=
fi

if [ -f "${install_conf}" ]; then
	echo "Reading ${install_conf}..."
	# shellcheck source=/dev/null
	. "${install_conf}"

	if [ -n "${layout}" ]; then
		echo "${install_conf} configures layout=${layout}"
		if [ -z "${INSTALLKERNEL_LAYOUT}" ]; then
			INSTALLKERNEL_LAYOUT="${layout}"
		fi
	fi
	if [ -n "${initrd_generator}" ]; then
		echo "${install_conf} configures initrd_generator=${initrd_generator}"
		if [ -z "${INSTALLKERNEL_INITRD_GENERATOR}" ]; then
			INSTALLKERNEL_INITRD_GENERATOR="${initrd_generator}"
		fi
	fi
	if [ -n "${uki_generator}" ]; then
		echo "${install_conf} configures uki_generator=${uki_generator}"
		if [ -z "${INSTALLKERNEL_UKI_GENERATOR}" ]; then
			INSTALLKERNEL_UKI_GENERATOR="${uki_generator}"
		fi
	fi
fi

export INSTALLKERNEL_LAYOUT
export INSTALLKERNEL_INITRD_GENERATOR
export INSTALLKERNEL_UKI_GENERATOR

if [ -f /etc/os-release ]; then
	# shellcheck source=/dev/null
	. /etc/os-release
elif [ -f /usr/lib/os-release ]; then
	# shellcheck source=/dev/null
	. /usr/lib/os-release
fi

# Set sane default if no or broken os-release
export NAME="${NAME:=Linux}"
export ID="${ID:=linux}"
export PRETTY_NAME="${PRETTY_NAME:=Linux}"

suffix=
if [ "${INSTALLKERNEL_LAYOUT}" = efistub ]; then
	if [ ${#} -le 3 ] || [ "${4%/boot}" != "${4}" ]; then
		# Relocate to ESP
		for candidate in "${dir}/EFI" "${dir}/efi" "${dir}" "${dir%/boot}/efi"; do
			if [ -d "${candidate}/EFI/${NAME}" ]; then
				echo "Found vendor directory on ESP"
				dir=${candidate}/EFI/${NAME}
				suffix=.efi
				# backwards compatibility
				if [ -f "/boot/intel-uc.img" ]; then
					cp "/boot/intel-uc.img" "${dir}/intel-uc.img"
				fi
				if [ -f "/boot/amd-uc.img" ]; then
					cp "/boot/amd-uc.img" "${dir}/amd-uc.img"
				fi
			else
				continue
			fi
		done
		if [ "${dir}" = "/boot" ] || [ "${dir}" = "${4}" ]; then
			echo "layout=efistub set, but did not find vendor directory on ESP"
			echo "Please create the EFI/${NAME} directory on the EFI System partition manually"
		fi
	fi
fi

# If installing in the usual directory, run the same scripts that hook
# into kernel package installation.  Also make sure the PATH includes
# /usr/sbin and /sbin, just as dpkg would.
if [ ${#} -le 3 ] || [ "${4}" = "/boot" ]; then
	(
		#shellcheck disable=SC2030
		export LC_ALL=C PATH="${PATH}:/usr/sbin:/sbin"

		if [ -z "${INSTALLKERNEL_PREINST_PLUGINS}" ]; then
			INSTALLKERNEL_PREINST_PLUGINS="$(
				dropindirs_sort \
				"/etc/kernel/preinst.d" \
				"/usr/lib/kernel/preinst.d"
			)"
		fi

		for plugin in ${INSTALLKERNEL_PREINST_PLUGINS}; do
			echo ""
			echo "Running ${plugin} ${ver} ${img}..."
			echo ""
			"${plugin}" "${ver}" "${img}"
			echo ""
			echo "Hook ${plugin} finished successfully"
			echo ""
		done
	)
else
	echo "WARNING: A custom installation directory is specified as fourth argument."
	echo "WARNING: In this configuration running installation hooks is not supported."
	echo "WARNING: All pre-installation hooks are ignored."
fi

# use the same input path as /usr/lib/kernel/install.d/50-dracut.install
# and the same output path as dracut and genkernel-4 for best interop
base_dir=$(dirname "${img}")
initrd=${base_dir}/initrd
uki=${base_dir}/uki.efi

if [ "${img%vmlinux*}" != "${img}" ]; then
	img_dest=vmlinux
else
	img_dest=vmlinuz
fi

# If we found a uki.efi, install it instead of kernel+initrd
if [ -f "${uki}" ] && [ "${INSTALLKERNEL_LAYOUT}" = uki ]; then
	suffix=.efi
	if [ ${#} -le 3 ] || [ "${4%/boot}" != "${4}" ]; then
		# Relocate to ESP
		for candidate in "${dir}/EFI" "${dir}/efi" "${dir}" "${dir%/boot}/efi"; do
			if [ -d "${candidate}/EFI/Linux" ]; then
				echo "Found UKI directory on ESP"
				dir=${candidate}/EFI/Linux
				img_dest=${ID}
			else
				continue
			fi
		done
		if [ "${dir}" = "/boot" ] || [ "${dir}" = "${4}" ]; then
			echo "layout=uki set, but did not find the UKI directory on ESP"
			echo "Please create the EFI/Linux directory on the EFI System partition manually"
		fi
	fi
	echo "Installing Unified Kernel Image for ${ver}..."
	updatever "${img_dest}" "${uki}" "${suffix}"
else
	echo "Installing kernel image for ${ver}..."
	updatever "${img_dest}" "${img}" "${suffix}"
	if [ -f "${initrd}" ]; then
		echo "Installing initramfs image for ${ver}..."
		updatever initramfs "${initrd}" .img
	fi

	echo "Installing System.map for ${ver}..."
	updatever System.map "${map}"

	config=$(dirname "${map}")
	config="${config}/.config"
	if [ -f "${config}" ]; then
		echo "Installing config for ${ver}..."
		updatever config "${config}"
	fi
fi

# If installing in the usual directory, run the same scripts that hook
# into kernel package installation.  Also make sure the PATH includes
# /usr/sbin and /sbin, just as dpkg would.
if [ ${#} -le 3 ] || [ "${4}" = "/boot" ]; then
	(
		#shellcheck disable=SC2031
		export LC_ALL=C PATH="${PATH}:/usr/sbin:/sbin"

		if [ -z "${INSTALLKERNEL_POSTINST_PLUGINS}" ]; then
			INSTALLKERNEL_POSTINST_PLUGINS="$(
				dropindirs_sort \
				"/etc/kernel/postinst.d" \
				"/usr/lib/kernel/postinst.d"
			)"
		fi

		for plugin in ${INSTALLKERNEL_POSTINST_PLUGINS}; do
			echo ""
			echo "Running ${plugin} ${ver} ${dir}/${img_dest}-${ver}${suffix}..."
			echo ""
			"${plugin}" "${ver}" "${dir}/${img_dest}-${ver}${suffix}"
			echo ""
			echo "Hook ${plugin} finished successfully"
			echo ""
		done
	)
else
	echo "WARNING: A custom installation directory is specified as fourth argument."
	echo "WARNING: In this configuration running installation hooks is not supported."
	echo "WARNING: All post-installation hooks are ignored."
fi

exit 0
