#!/bin/bash
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.

. /usr/lib/kdump/kdump-read-config.sh || exit 1

FORCE=false
QUIET=false
DEBUG=false
INITRD_DEFAULT=/var/lib/kdump/initrd
INITRD=$INITRD_DEFAULT

function usage()
{
	echo "mkdumprd - Create an initrd for kdump"
	echo ""
	echo "This script uses dracut(8) internally. Options:"
	echo ""
	echo "   -k <kernel>  or"
	echo "   -K <kernel>      Overrides KDUMP_KERNELVER."
	echo "   -I <initrd>      Output file for initrd ($INITRD_DEFAULT by default)"
	echo "   -f               Force regeneration even if the configuration"
	echo "                    did not change."
	echo "   -q               Quiet (don't print status messages)."
	echo "   -d               Output debug information of the initrd build process."
	echo "   -h               Print this help."
	echo "   -F               internal, don't use; build embedded FADUMP initrd"
}

# sets KDUMP_DRACUT_MOUNT_OPTION for targets mounted by dracut
function get_mount()
{
	# dracut needs to mount the target directory for the
	# file, nfs and cifs protocols
	MOUNTPOINT="/kdump/mnt"
	case ${KDUMP_PROTO} in 
		file)
			# dereference symlinks, because they might not work in the
			# kdump environment when the directory is mounted elsewhere
			KDUMP_SAVEDIR_REALPATH=$(realpath -m "${KDUMP_SAVEDIR#*://}")
			mkdir -p "${KDUMP_SAVEDIR_REALPATH}"
			read -r SOURCE TARGET FS OPTIONS < <(findmnt -n -v --raw --target "${KDUMP_SAVEDIR_REALPATH}" --output=source,target,fstype,options) ||
				error "Cannot find mount point for ${KDUMP_SAVEDIR#*://}"

			# get persistent device name for SOURCE
			local _symlink
			for _symlink in $(udevadm info --root --query=symlink "$SOURCE")
			do
			    case "$_symlink" in
				*/by-path/*|*/mapper/*)
				    SOURCE="$_symlink"
				    break
			    esac
			done

			TARGET="${MOUNTPOINT}${TARGET}"
			KDUMP_DRACUT_MOUNT_OPTION="${SOURCE} ${TARGET} ${FS} ${OPTIONS}"
			;;
		cifs)
                        # split URL into host, directory, user and password parts
			URL="${KDUMP_SAVEDIR#*://}"
                        DIR="/${URL#*/}"    
                        HOST="${URL%%/*}"  
			UPW="${HOST%%@*}"
			HOST="${HOST#*@}"
			USER=
			PW=
			if ! [[ ${UPW} == ${HOST} ]]; then
				USER="${UPW%%:*}"
                        	PW="${UPW#*:}"    
				[[ ${PW} == ${UPW} ]] && PW=""
			fi
			
			KDUMP_DRACUT_MOUNT_OPTION="//${HOST}${DIR} ${MOUNTPOINT} cifs user=$USER,password=$PW"
			;;
		nfs)
                        # split URL into host, directory, user and password parts
                        URL="${KDUMP_SAVEDIR#*://}"
                        DIR="/${URL#*/}"    
                        HOST="${URL%%/*}"  
			
			KDUMP_DRACUT_MOUNT_OPTION="${HOST}:${DIR} ${MOUNTPOINT} nfs nolock"
			;;
		*)
			KDUMP_DRACUT_MOUNT_OPTION=""
			;;
	esac
}

# check if config has changed since a given initrd has been generated
function initrd_up_to_date()
{
	local INITRD=$1

	# no initrd yet
	[[ -f "$INITRD" ]] || return 1

	# config changed
	[[ /etc/sysconfig/kdump -ot "$INITRD" ]] || return 1

	# network settings changed
	if [[ $KDUMP_PROTO != file ]]; then
		[[ /etc/hosts -ot "$INITRD" ]] || return 1
		[[ /etc/nsswitch.conf -ot "$INITRD" ]] || return 1
		
		# SSH keys changed
		if [[ $KDUMP_PROTO == ssh ]] || [[ $KDUMP_PROTO == sftp ]]; then
			pushd ~root/.ssh >/dev/null #identity files are relative to this directory
			[[ -z "${KDUMP_SSH_IDENTITY}" ]] && KDUMP_SSH_IDENTITY="id_rsa id_dsa id_ecdsa id_ed25519"
			for i in ${KDUMP_SSH_IDENTITY}; do
				[[ -f  "${i}.pub" ]] || continue
				[[ "${i}.pub" -ot "$INITRD" ]] || return 1
			done
			popd
		fi
	fi
	return 0
}

function error()
{
	echo "$1" >&2
	exit 1
}


# Option parsing                                                             {{{
while getopts "hfqk:K:I:dF" name ; do
    case $name in
        f)  FORCE=true
            ;;

        h)  usage
            exit 0
            ;;

        q)  QUIET=true
            ;;

        k|K)  KDUMP_KERNELVER="$OPTARG"
            ;;

        I)  INITRD=$OPTARG
            ;;
        
	d)  DEBUG=true
	    set -x
            ;;

	F)  FADUMP_INTERNAL=true
	    ;;

        ?)  usage
            exit 1
            ;;
    esac
done
shift $(($OPTIND -1))

# With FADUMP, we don't know which kernel will be booted after a crash.
# Kdump initrd is embedded in all standard initrds by the zz-fadumpinit module 
# which calls this script with -F.
#
# If we're called in any other way (i.e. without -F), we 
# regenerate all initrds that are not up to date using dracut.
# zz-fadumpinit will call us again with -F to build the kdump part of each initrd.
if [[ "$KDUMP_FADUMP" == "true" ]] && [[ "$FADUMP_INTERNAL" != "true" ]]; then
	DEBUG_ARG=""
	$DEBUG && DEBUG_ARG="--debug"

	# cycle through all installed kernels
	# (based on dracut's  handling of --regenerate-all)
	cd /lib/modules || exit 1
	RET=0
	for i in *; do
		[[ -f $i/modules.dep ]] || [[ -f $i/modules.dep.bin ]] || continue
		[[ -d $i/kernel ]] || continue
		INITRD="/boot/initrd-$i"
		ABS_INITRD=$(readlink -f "$INITRD") && INITRD="$ABS_INITRD"
		
		UPDATE=$FORCE
		# config changed or INITRD does not exist?
		$UPDATE || initrd_up_to_date "$INITRD" || UPDATE=true

		if ! $UPDATE; then
			# initrd up to date, nothing to do
			$QUIET || echo "Not regenerating $INITRD. Use mkdumprd -f to force regeneration."
			continue
		fi
		
		if ! [[ -w /boot ]]; then
			echo "/boot not writable, not regenerating initrds."
			exit 0
		fi

		echo "Regenerating $INITRD ..." >&2
		dracut --force --kver="$i" $DEBUG_ARG 
		((RET += $?))
	done
	
	# remove the kernel symlink and $INITRD_DEFAULT, they are not used for FADUMP
	rm -f /var/lib/kdump/kernel "$INITRD_DEFAULT"

	exit "$RET"
fi

# KDUMP_KERNELVER may be an absolute path name or a kernel version
if [[ ${KDUMP_KERNELVER:0:1} == "/" ]]; then
	# absolute path
	KERNEL=${KDUMP_KERNELVER}
	if ! [ -f "$KERNEL" ] ; then
	    echo "Kernel $KERNEL does not exist."
	    exit 1
	fi
else
	# if a specific version is requested, look for it 
	# in /boot/$IMAGE-$KDUMP_KERNELVER
	# otherwise use the default kernel symlink /boot/$IMAGE
	# IMAGE names are different for different architectures
	
	[[ -n "${KDUMP_KERNELVER}" ]] && KDUMP_KERNELVER="-${KDUMP_KERNELVER}"
	for i in vmlinuz image Image vmlinux; do
		KERNEL="/boot/${i}${KDUMP_KERNELVER}"
		[[ -f $KERNEL ]] && break
	done
fi

if ! [ -f "$KERNEL" ] ; then
    echo "Kernel not found."
    exit 1
fi

KERNEL_REALPATH=$(realpath -e "$KERNEL")
if ! [ -f "$KERNEL_REALPATH" ] ; then
    echo "Kernel $KERNEL is an invalid symlink."
    exit 1
fi

# check if we need to regenerate the initrd
UPDATE=$FORCE

# config changed or INITRD does not exist?
$UPDATE || initrd_up_to_date "$INITRD" || UPDATE=true

# kernel changed?
KERNEL_CURRENT=$(realpath -qe /var/lib/kdump/kernel)
[[ "$KERNEL_REALPATH" == "$KERNEL_CURRENT" ]] || UPDATE=true

if ! $UPDATE; then
	# initrd is up to date, nothing to do
	$QUIET || echo "Not regenerating kdump initrd. Use mkdumprd -f to force regeneration."
        exit 0
fi

INITRD_DIR=$(dirname "$INITRD") 
if ! [[ -w "$INITRD_DIR" ]]; then
	echo "$INITRD_DIR not writable, not regenerating initrd."
	exit 0
fi

# transactional update has a fake /var
if [[ -n "$TRANSACTIONAL_UPDATE" ]] && [[ "$FADUMP_INTERNAL" != "true" ]]; then
	echo "Cannot update initrd within a transactional update."
	exit 0
fi

# build the initrd
get_mount

KERNELVERSION=$(get_kernel_version "$KERNEL")
declare -a DRACUT_ARGS
# arguments common with FADUMP
DRACUT_ARGS=(
        "--force"
        "--hostonly"
	"--hostonly-cmdline"
	"--no-hostonly-default-device" 
        "--add" "kdump"
        "--omit" "plymouth resume usrmount zz-fadumpinit"
)

[[ -n "${KDUMP_DRACUT_MOUNT_OPTION}" ]] && DRACUT_ARGS+=("--mount" "${KDUMP_DRACUT_MOUNT_OPTION}")
$DEBUG && DRACUT_ARGS+=("--debug")

if [[ "$FADUMP_INTERNAL" == "true" ]]; then
	# additional FADUMP arguments
	DRACUT_ARGS+=(
        "--no-compress"
        "--no-early-microcode"
	)
else
	# additional non-FADUMP arguments
	DRACUT_ARGS+=(
	"--compress" "xz -0 --check=crc32" 
	)
fi

echo "Regenerating kdump initrd ..." >&2
dracut "${DRACUT_ARGS[@]}" "$INITRD" "$KERNELVERSION" || exit

# if we're rebuilding the default INITRD, update the kernel symlink
# to point to the kernel image the initrd was just built for
[[ "$INITRD" == "$INITRD_DEFAULT" ]] && ln -sf "$KERNEL_REALPATH" /var/lib/kdump/kernel
exit 0
