#!/bin/bash -eu
# SPDX-License-Identifier: GPL-2.0-or-later
#
# List or debug kdumps
#
# Copyright (C) 2022 Vitaly Chikunov <vt@altlinux.org>
#
# shellcheck disable=SC2206,SC2128,SC1007

shopt -s nullglob
export LC_ALL=C

show_help()
{
	echo "kdumpctl [OPTIONS...] COMMAND ..."
	echo
	echo "List or debug kdumps."
	echo
	echo "Commands:"
	echo "  list [MATCHES...]  List available kdumps (default)"
	echo "  dmesg [MATCHES...] Show dmesg for matching kdump"
	echo "  debug [MATCHES...] Start a debugger (crash) for matching kdump"
	echo
	echo "Options:"
	echo "  -h, --help            Show this help"
	echo "  -r, --reverse         Show the newest entries first"
	echo
}

fatal()
{
	echo >&2 "Error: $*"
	exit 1
}

show=size
reverse=
json=
for opt do
	shift
	case "$opt" in
		-h | --help) show_help; exit 0 ;;
		--build-id | --dump | --dmesg | --size) show="${opt#--}" ;;
		-r | --reverse) reverse=-r ;;
		--json) json="--json --table-name kdump" ;;
		-*) fatal "Unknown option: '$opt'." ;;
		*) set -- "$@" "$opt"  ;;
	esac
done
COMMAND=${1-list}
MATCH=${2-}

kdump_vmcoreinfo()
{
	# There is VMCOREINFO note blob at the beginning of Kdump.
	VMCOREINFO=$(head -111111c "$KDUMPFILE" | grep -m1 -z 'OSRELEASE=' | tr -d '\0')
	OSRELEASE=$(echo "$VMCOREINFO" | grep -oP '^OSRELEASE=\K.*')
	BUILDID=$(echo "$VMCOREINFO" | grep -oP '^BUILD-ID=\K.*')
}

_kdump_list()
{
	local justone=${1-} firstmatch=

	if [ -n "$justone" ]; then
		reverse=-r
		firstmatch=-m1
	fi
	# shellcheck disable=SC2045
	for d in $(ls -d $reverse /var/crash/*/); do
		n=${d%/}
		n=${n#/var/crash/}
		ts="${n:0:4}-${n:4:2}-${n:6:2} ${n:8:2}:${n:10:2}"
		kdump_file "$n"
		[ -n "$KDUMPFILE" ] || continue
		kdump_vmcoreinfo
		T="$ts  OSRELEASE=${OSRELEASE:-unknown}"
		case "$show" in
			build-id) T+="  BUILD-ID=${BUILDID:-unknown}" ;;
			dump)     T+="  $KDUMPFILE" ;;
			dmesg)    T+="  $DMESGFILE" ;;
			size)     T+="  $(du -sh "$d" | grep -Eo '^\S+')"
		esac
		echo "$T"
	done | grep --color=auto -P $firstmatch -e "$MATCH" || fatal "No kdumps found."
}

kdump_list()
{
	# shellcheck disable=SC2086
	_kdump_list | column -t --table-columns="DATE,TIME,VMCOREINFO,${show^^}" $json
}

kdump_match()
{
	M=$(_kdump_list 1)
	M=${M%%  *}
	M=${M//[-: ]/}
}

kdump_file()
{
	KDUMPFILE=( /var/crash/$1/vmcore* /var/crash/$1/*dump* )
	DMESGFILE=( /var/crash/$1/dmesg* )
}

kdump_debug()
{
	local vmlinux

	kdump_match
	kdump_file "$M"
	[ -n "$KDUMPFILE" ] || fatal "Kdump file not found."
	kdump_vmcoreinfo
	if [ -n "$BUILDID" ]; then
		BU=${BUILDID:0:2}
		ILDID=${BUILDID:2}
		vmlinux=/usr/lib/debug/.build-id/$BU/$ILDID
		[ -h "$vmlinux" ] && vmlinux=$(readlink -fn "$vmlinux")
	fi
	if [ ! -e "$vmlinux" ] && [ -n "$OSRELEASE" ]; then
		vmlinux=/usr/lib/debug/lib/modules/$OSRELEASE/vmlinux
	else
		fatal "Identity of the Kdump '$KDUMPFILE' unknown"
	fi
	if [ ! -e "$vmlinux" ]; then
		local flv=$OSRELEASE pkg=debuginfo
		flv=${flv#*-}
		flv=${flv%-*}
		[ -n "$flv" ] && [ "$flv" != "$OSRELEASE" ] && pkg="kernel-image-$flv-debuginfo"
		fatal "No vmlinux${OSRELEASE:+ for $OSRELEASE} found, perhaps $pkg needs to be installed."
	fi
	type -p crash >/dev/null || fatal "crash binary not found, perhaps crash package needs to be installed."
	CRASH=crash
	(set -x; $CRASH "$vmlinux" "$KDUMPFILE")
}

kdump_dmesg()
{
	local dmesg

	kdump_match
	dmesg=( /var/crash/$M/dmesg* )
	[ -n "$dmesg" ] || fatal "dmesg file not found."
	less "$dmesg"
}

case "$COMMAND" in
	list | '') kdump_list ;;
	debug) kdump_debug ;;
	dmesg) kdump_dmesg ;;
	*) fatal "Unknown command: '$COMMAND'." ;;
esac
