fadump: isolate fadump initramfs image within the default one

From: Hari Bathini <hbathini@linux.ibm.com>

In case of fadump, the initramfs image has to be built to boot into
the production environment as well as to offload the active crash dump
to the specified dump target (for boot after crash). As the same image
would be used for both boot scenarios, it could not be built optimally
while accommodating both cases.

Use --include to include the initramfs image built for offloading
active crash dump to the specified dump target. Also, introduce a new
out-of-tree dracut module (99zz-fadumpinit) that installs a customized
init program while moving the default /init to /init.dracut. This
customized init program is leveraged to isolate fadump image within
the default initramfs image by kicking off default boot process
(exec /init.dracut) for regular boot scenario and activating fadump
initramfs image, if the system is booting after a crash.

If squash is available, ensure default initramfs image is also built
with squash module to reduce memory consumption in capture kernel.

Signed-off-by: Hari Bathini <hbathini@linux.ibm.com>
Signed-off-by: Kairui Song <kasong@redhat.com>
---
 dracut-fadump-init-fadump.sh  |   44 ++++++++++++++++++++++++++++++
 dracut-fadump-module-setup.sh |   23 ++++++++++++++++
 dracut-module-setup.sh        |   16 +----------
 kdump-lib.sh                  |   10 +++++++
 kdumpctl                      |   22 +++------------
 kexec-tools.spec              |   14 ++++++++++
 mkdumprd                      |    5 +++
 mkfadumprd                    |   60 +++++++++++++++++++++++++++++++++++++++++
 8 files changed, 162 insertions(+), 32 deletions(-)
 create mode 100755 dracut-fadump-init-fadump.sh
 create mode 100644 dracut-fadump-module-setup.sh
 create mode 100644 mkfadumprd

diff --git a/dracut-fadump-init-fadump.sh b/dracut-fadump-init-fadump.sh
new file mode 100755
index 0000000..5468d99
--- /dev/null
+++ b/dracut-fadump-init-fadump.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+export PATH=/usr/bin:/usr/sbin
+export SYSTEMD_IN_INITRD=lenient
+
+[ -e /proc/mounts ] ||
+	(mkdir -p /proc && mount -t proc -o nosuid,noexec,nodev proc /proc)
+
+grep -q '^sysfs /sys sysfs' /proc/mounts ||
+	(mkdir -p /sys && mount -t sysfs -o nosuid,noexec,nodev sysfs /sys)
+
+grep -q '^none / ' /proc/mounts || grep -q '^rootfs / ' /proc/mounts && ROOTFS_IS_RAMFS=1
+
+if [ -f /proc/device-tree/rtas/ibm,kernel-dump ] || [ -f /proc/device-tree/ibm,opal/dump/mpipl-boot ]; then
+	mkdir /newroot
+	mount -t ramfs ramfs /newroot
+
+	if [ $ROOTFS_IS_RAMFS ]; then
+		for FILE in $(ls -A /fadumproot/); do
+			mv /fadumproot/$FILE /newroot/
+		done
+		exec switch_root /newroot /init
+	else
+		mkdir /newroot/sys /newroot/proc /newroot/oldroot
+		mount --move /proc /newroot/proc
+		mount --move /sys /newroot/sys
+		cp --reflink=auto --sparse=auto --preserve=mode,timestamps,links -dfr /fadumproot/. /newroot/
+		cd /newroot && pivot_root . oldroot
+
+		loop=1
+		while [ $loop ]; do
+			unset loop
+			while read -r _ mp _; do
+				case $mp in
+				/oldroot/*) umount "$mp" && loop=1 ;;
+				esac
+			done </proc/mounts
+		done
+		umount -l oldroot
+
+		exec /init
+	fi
+else
+	exec /init.dracut
+fi
diff --git a/dracut-fadump-module-setup.sh b/dracut-fadump-module-setup.sh
new file mode 100644
index 0000000..f062486
--- /dev/null
+++ b/dracut-fadump-module-setup.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+check() {
+    return 255
+}
+
+depends() {
+    return 0
+}
+
+install() {
+    mv -f "$initdir/init" "$initdir/init.dracut"
+    inst_script "$moddir/init-fadump.sh" /init
+    chmod a+x "$initdir/init"
+
+    # Install required binaries for the init script (init-fadump.sh)
+    inst_multiple sh modprobe grep mkdir mount
+    if dracut_module_included "squash"; then
+        inst_multiple cp pivot_root umount
+    else
+        inst_multiple ls mv switch_root
+    fi
+}
diff --git a/dracut-module-setup.sh b/dracut-module-setup.sh
index 35a961a..c8d7f08 100755
--- a/dracut-module-setup.sh
+++ b/dracut-module-setup.sh
@@ -27,17 +27,7 @@ depends() {
         [[ " $omit_dracutmodules " != *\ $1\ * ]] && _dep="$_dep $1"
     }
 
-    is_squash_available() {
-        for kmodule in squashfs overlay loop; do
-            if [ -z "$KDUMP_KERNELVER" ]; then
-                modprobe --dry-run $kmodule &>/dev/null || return 1
-            else
-                modprobe -S $KDUMP_KERNELVER --dry-run $kmodule &>/dev/null || return 1
-            fi
-        done
-    }
-
-    if is_squash_available && ! is_fadump_capable; then
+    if is_squash_available; then
         add_opt_module squash
     else
         dwarning "Required modules to build a squashed kdump image is missing!"
@@ -1085,7 +1075,5 @@ install() {
       ${initdir}/etc/lvm/lvm.conf &>/dev/null
 
     # Save more memory by dropping switch root capability
-    if ! is_fadump_capable; then
-        dracut_no_switch_root
-    fi
+    dracut_no_switch_root
 }
diff --git a/kdump-lib.sh b/kdump-lib.sh
index 27741fb..8e618f8 100755
--- a/kdump-lib.sh
+++ b/kdump-lib.sh
@@ -19,6 +19,16 @@ is_fadump_capable()
     return 1
 }
 
+is_squash_available() {
+    for kmodule in squashfs overlay loop; do
+        if [ -z "$KDUMP_KERNELVER" ]; then
+            modprobe --dry-run $kmodule &>/dev/null || return 1
+        else
+            modprobe -S $KDUMP_KERNELVER --dry-run $kmodule &>/dev/null || return 1
+        fi
+    done
+}
+
 perror_exit() {
     derror "$@"
     exit 1
diff --git a/kdumpctl b/kdumpctl
index 978dae5..26247d1 100755
--- a/kdumpctl
+++ b/kdumpctl
@@ -8,6 +8,7 @@ KEXEC_ARGS=""
 KDUMP_CONFIG_FILE="/etc/kdump.conf"
 KDUMP_LOG_PATH="/var/log"
 MKDUMPRD="/sbin/mkdumprd -f"
+MKFADUMPRD="/sbin/mkfadumprd"
 DRACUT_MODULES_FILE="/usr/lib/dracut/modules.txt"
 SAVE_PATH=/var/crash
 SSH_KEY_LOCATION="/root/.ssh/kdump_id_rsa"
@@ -104,25 +105,10 @@ save_core()
 
 rebuild_fadump_initrd()
 {
-	local target_initrd_tmp
-
-	# this file tells the initrd is fadump enabled
-	touch /tmp/fadump.initramfs
-	target_initrd_tmp="$TARGET_INITRD.tmp"
-	ddebug "rebuild fadump initrd: $target_initrd_tmp $DEFAULT_INITRD_BAK $KDUMP_KERNELVER"
-	$MKDUMPRD $target_initrd_tmp --rebuild $DEFAULT_INITRD_BAK --kver $KDUMP_KERNELVER \
-		-i /tmp/fadump.initramfs /etc/fadump.initramfs
-	if [ $? != 0 ]; then
-		derror "mkdumprd: failed to rebuild initrd with fadump support"
-		rm -f /tmp/fadump.initramfs
+	if ! $MKFADUMPRD "$DEFAULT_INITRD_BAK" "$TARGET_INITRD" --kver "$KDUMP_KERNELVER"; then
+		derror "mkfadumprd: failed to make fadump initrd"
 		return 1
 	fi
-	rm -f /tmp/fadump.initramfs
-
-	# updating fadump initrd
-	ddebug "updating fadump initrd: $target_initrd_tmp $TARGET_INITRD"
-	mv $target_initrd_tmp $TARGET_INITRD
-	sync
 
 	return 0
 }
@@ -612,7 +598,7 @@ check_rebuild()
 		#in case of fadump mode, check whether the default/target
 		#initrd is already built with dump capture capability
 		if [ "$DEFAULT_DUMP_MODE" == "fadump" ]; then
-			capture_capable_initrd=$(lsinitrd -f $DRACUT_MODULES_FILE $TARGET_INITRD | grep ^kdumpbase$ | wc -l)
+			capture_capable_initrd=$(lsinitrd -f $DRACUT_MODULES_FILE $TARGET_INITRD | grep -e ^kdumpbase$ -e ^zz-fadumpinit$ | wc -l)
 		fi
 	fi
 
diff --git a/kexec-tools.spec b/kexec-tools.spec
index 7b29b0e..f43c186 100644
--- a/kexec-tools.spec
+++ b/kexec-tools.spec
@@ -39,6 +39,7 @@ Source28: kdump-udev-throttler
 Source29: kdump.sysconfig.aarch64
 Source30: 60-kdump.install
 Source31: kdump-logger.sh
+Source32: mkfadumprd
 
 #######################################
 # These are sources for mkdumpramfs
@@ -54,6 +55,9 @@ Source107: dracut-kdump-emergency.target
 Source108: dracut-early-kdump.sh
 Source109: dracut-early-kdump-module-setup.sh
 
+Source200: dracut-fadump-init-fadump.sh
+Source201: dracut-fadump-module-setup.sh
+
 Requires(post): systemd-units
 Requires(preun): systemd-units
 Requires(postun): systemd-units
@@ -176,6 +180,7 @@ SYSCONFIG=$RPM_SOURCE_DIR/kdump.sysconfig.%{_target_cpu}
 install -m 644 $SYSCONFIG $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/kdump
 
 install -m 755 %{SOURCE7} $RPM_BUILD_ROOT/usr/sbin/mkdumprd
+install -m 755 %{SOURCE32} $RPM_BUILD_ROOT/usr/sbin/mkfadumprd
 install -m 644 %{SOURCE8} $RPM_BUILD_ROOT%{_sysconfdir}/kdump.conf
 install -m 644 kexec/kexec.8 $RPM_BUILD_ROOT%{_mandir}/man8/kexec.8
 install -m 644 %{SOURCE12} $RPM_BUILD_ROOT%{_mandir}/man8/mkdumprd.8
@@ -211,6 +216,7 @@ install -m 644 makedumpfile-%{mkdf_ver}/eppic_scripts/* $RPM_BUILD_ROOT/usr/shar
 
 %define remove_dracut_prefix() %(echo -n %1|sed 's/.*dracut-//g')
 %define remove_dracut_early_kdump_prefix() %(echo -n %1|sed 's/.*dracut-early-kdump-//g')
+%define remove_dracut_fadump_prefix() %(echo -n %1|sed 's/.*dracut-fadump-//g')
 
 # deal with dracut modules
 mkdir -p -m755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99kdumpbase
@@ -228,6 +234,13 @@ cp %{SOURCE108} $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99earlyk
 cp %{SOURCE109} $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99earlykdump/%{remove_dracut_early_kdump_prefix %{SOURCE109}}
 chmod 755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99earlykdump/%{remove_dracut_prefix %{SOURCE108}}
 chmod 755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99earlykdump/%{remove_dracut_early_kdump_prefix %{SOURCE109}}
+%ifarch ppc64 ppc64le
+mkdir -p -m755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99zz-fadumpinit
+cp %{SOURCE200} $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99zz-fadumpinit/%{remove_dracut_fadump_prefix %{SOURCE200}}
+cp %{SOURCE201} $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99zz-fadumpinit/%{remove_dracut_fadump_prefix %{SOURCE201}}
+chmod 755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99zz-fadumpinit/%{remove_dracut_fadump_prefix %{SOURCE200}}
+chmod 755 $RPM_BUILD_ROOT/etc/kdump-adv-conf/kdump_dracut_modules/99zz-fadumpinit/%{remove_dracut_fadump_prefix %{SOURCE201}}
+%endif
 
 
 %define dracutlibdir %{_prefix}/lib/dracut
@@ -309,6 +322,7 @@ done
 /usr/sbin/makedumpfile
 %endif
 /usr/sbin/mkdumprd
+/usr/sbin/mkfadumprd
 /usr/sbin/vmcore-dmesg
 %{_bindir}/*
 %{_datadir}/kdump
diff --git a/mkdumprd b/mkdumprd
index 6d699c3..89a160d 100644
--- a/mkdumprd
+++ b/mkdumprd
@@ -452,6 +452,11 @@ then
     add_dracut_arg "--add-drivers" \"$extra_modules\"
 fi
 
+# TODO: The below check is not needed anymore with the introduction of
+#       'zz-fadumpinit' module, that isolates fadump's capture kernel initrd,
+#       but still sysroot.mount unit gets generated based on 'root=' kernel
+#       parameter available in fadump case. So, find a way to fix that first
+#       before removing this check.
 if ! is_fadump_capable; then
     # The 2nd rootfs mount stays behind the normal dump target mount,
     # so it doesn't affect the logic of check_dump_fs_modified().
diff --git a/mkfadumprd b/mkfadumprd
new file mode 100644
index 0000000..4af4ae6
--- /dev/null
+++ b/mkfadumprd
@@ -0,0 +1,60 @@
+#!/bin/bash --norc
+# Generate an initramfs image that isolates dump capture capability within
+# the default initramfs using zz-fadumpinit dracut module.
+
+if [ -f /etc/sysconfig/kdump ]; then
+	. /etc/sysconfig/kdump
+fi
+
+[[ $dracutbasedir ]] || dracutbasedir=/usr/lib/dracut
+. $dracutbasedir/dracut-functions.sh
+. /lib/kdump/kdump-lib.sh
+. /lib/kdump/kdump-logger.sh
+
+#initiate the kdump logger
+if ! dlog_init; then
+	echo "mkfadumprd: failed to initiate the kdump logger."
+	exit 1
+fi
+
+readonly MKFADUMPRD_TMPDIR="$(mktemp -d -t mkfadumprd.XXXXXX)"
+[ -d "$MKFADUMPRD_TMPDIR" ] || perror_exit "mkfadumprd: mktemp -d -t mkfadumprd.XXXXXX failed."
+trap '
+    ret=$?;
+    [[ -d $MKFADUMPRD_TMPDIR ]] && rm --one-file-system -rf -- "$MKFADUMPRD_TMPDIR";
+    exit $ret;
+    ' EXIT
+
+# clean up after ourselves no matter how we die.
+trap 'exit 1;' SIGINT
+
+MKDUMPRD="/sbin/mkdumprd -f"
+# Default boot initramfs to be rebuilt
+REBUILD_INITRD="$1" && shift
+TARGET_INITRD="$1" && shift
+FADUMP_INITRD="$MKFADUMPRD_TMPDIR/fadump.img"
+
+### First build an initramfs with dump capture capability
+# this file tells the initrd is fadump enabled
+touch "$MKFADUMPRD_TMPDIR/fadump.initramfs"
+ddebug "rebuild fadump initrd: $FADUMP_INITRD $DEFAULT_INITRD $KDUMP_KERNELVER"
+if ! $MKDUMPRD "$FADUMP_INITRD" -i "$MKFADUMPRD_TMPDIR/fadump.initramfs" /etc/fadump.initramfs; then
+	perror_exit "mkfadumprd: failed to build image with dump capture support"
+fi
+
+### Unpack the initramfs having dump capture capability
+mkdir -p "$MKFADUMPRD_TMPDIR/fadumproot"
+if ! (pushd "$MKFADUMPRD_TMPDIR/fadumproot" > /dev/null && lsinitrd --unpack "$MKFADUMPRD_TMPDIR/fadump.img" && \
+	popd > /dev/null); then
+	derror "mkfadumprd: failed to unpack '$MKFADUMPRD_TMPDIR'"
+	exit 1
+fi
+
+### Pack it into the normal boot initramfs with zz-fadumpinit module
+_dracut_isolate_args="--rebuild $REBUILD_INITRD --add zz-fadumpinit -i $MKFADUMPRD_TMPDIR/fadumproot /fadumproot"
+if is_squash_available; then
+	_dracut_isolate_args="$_dracut_isolate_args --add squash"
+fi
+if ! dracut --force --quiet $_dracut_isolate_args $@ "$TARGET_INITRD"; then
+	perror_exit "mkfadumprd: failed to setup '$TARGET_INITRD' with dump capture capability"
+fi
