#! /bin/sh
#
#  Copyright 2005 Red Hat, Inc.
#  Author:  Jeff Moyer <jmoyer@redhat.com>
#  Modifications for SUSE from Chris Mason <mason@suse.com>
#                              Takashi Iwai <tiwai@suse.de>
#
#  kdump
#
#  Description:  The kdump init script provides the support necessary for
#  		 loading a kdump kernel into memory at system bootup time,
#		 and for copying away a vmcore at system panic time.
#
#
# /etc/init.d/kexec
### BEGIN INIT INFO
# Provides:       kdump
# Required-Start: boot.localfs $remote_fs
# Should-Start:   
# Required-Stop:  
# Default-Start:  1 2 3 5
# Default-Stop:
# Description:    kdump core saving and boot configuration
### END INIT INFO

. /etc/sysconfig/kdump
. /etc/rc.status

KEXEC=/sbin/kexec
KDUMP_HELPER=/usr/sbin/kdump-helper

BOOTDIR="/boot"

# purge old dump directories if the number of directories
# exceeds $KDUMP_KEEP_OLD_DUMPS
purge_old_dumps()
{
    dirs=`ls -d $KDUMP_SAVEDIR/*-*-*-*:* 2>/dev/null | sort`
    numdirs=`echo $dirs | wc -w`
    for d in $dirs; do
	if [ $numdirs -le $KDUMP_KEEP_OLD_DUMPS ]; then
	    break;
	fi
	echo " Expiring old dump $d"
	rm -rf $d
	numdirs=`expr $numdirs - 1`
    done
}

# get the free disk space of the given directory in MB
parse_rest_size()
{
    test -d "$1" || mkdir -p "$1"
    hdread=""
    df -P "$1" | while read fs bl us av rest; do
	test -n "$hdread" && expr $av / 1024
	hdread="$fs"
    done
}

# get vmcore size in MB
get_mem_size()
{
    s=`stat -c '%s' /proc/vmcore`
    expr \( $s + 1048575 \) / 1048576
}

get_size_mb()
{
    s=`stat -c '%s' $1 2> /dev/null || echo 0`
    expr \( $s + 1048575 \) / 1048576
}

# The default dumper
#
# Clean up old stuff if necessary, check the free size
# and save the vmcore
save_core()
{
    dumpsize=`get_mem_size`
    if [ -z "$dumpsize" -o "$dumpsize" = 0 ]; then
	echo -n "Null size vmcore"
	rc_status -s
	rc_failed 6
	return
    fi

    if [ $KDUMP_KEEP_OLD_DUMPS -gt 0 ]; then
	purge_old_dumps
    fi

    if [ $KDUMP_FREE_DISK_SIZE -gt 0 ]; then
	restsize=`parse_rest_size "$KDUMP_SAVEDIR"`
	needsize=`expr $dumpsize + $KDUMP_FREE_DISK_SIZE`
	if [ $restsize -lt $needsize ]; then
	    echo -n " No enough space left on dump device ($restsize MB)"
	    rc_status -s
	    rc_failed 6
	    return
	fi
    fi

    coredir="${KDUMP_SAVEDIR}/`date +"%Y-%m-%d-%H:%M"`"
    mkdir -p $coredir

    echo -n "Saving $dumpsize MB crash dump to $coredir"
    if [ $(($KDUMP_VERBOSE & 2)) -gt 0 ] ; then
	echo " ..."
	/bin/cp --sparse=always /proc/vmcore $coredir/vmcore &
	pid=$!
	sleep 5
	while true; do
	    copied=`get_size_mb $coredir/vmcore`
	    printf "Copied %'10llu (%3d%%)     \r" \
		$copied $(($copied * 100 / $dumpsize))
	    test -z "$pid" && break
	    test -d "/proc/$pid" || pid=""
	    sleep 15
	done
    else
	/bin/cp --sparse=always /proc/vmcore $coredir/vmcore
    fi
    rc_status -v
}

# print the available kdump kernel path
# empty if no matching file is found
check_boot_kernel ()
{
    local kstr
    kstr="${BOOTDIR}/vmlinux-$1$2"
    if [ -f $kstr ]; then
	echo $kstr
	return
    fi
    kstr="$kstr.gz"
    if [ -f $kstr ]; then
	echo $kstr
	return
    fi
    case `uname -i` in
    ia64)
	# ia64 uses vmlinuz as of vmlinux.gz
	kstr="${BOOTDIR}/vmlinuz-$1$2"
	if [ -f $kstr ]; then
	    echo $kstr
	    return
	fi
	;;
    esac
}


# Load the kdump kerel specified in /etc/sysconfig/kdump
# If none is specified, try to load a kdump kernel with the same version
# as the currently running kernel.
load_kdump()
{
    echo -n "Loading kdump "
    if [ -z "$KDUMP_KERNELVER" ]; then
	kdump_kver=`uname -r | sed -e's/-[^-]*$//g'`
	kdump_kernel=`check_boot_kernel $kdump_kver -kdump`
	if [ -n "$kdump_kernel" ]; then
	    kdump_kver="${kdump_kver}-kdump"
	elif [ -z "$kdump_kernel" ]; then
	    kdump_kver=`uname -r`
	    kdump_kernel=`check_boot_kernel $kdump_kver`
	fi
    else
	kdump_kver="$KDUMP_KERNELVER"
	kdump_kernel=`check_boot_kernel $kdump_kver`
    fi

    if [ -z "$kdump_kernel" -o ! -f "$kdump_kernel" ]; then
	echo -n ": No kdump kernel image found for kernel $kdump_kver." 
	rc_status -s
	rc_failed 6
	rc_exit
    fi

    kdump_initrd="${BOOTDIR}/initrd-${kdump_kver}"

    if [ ! -f $kdump_initrd ]; then
	echo -n ": No kdump initial ramdisk found."
	echo "Tried to locate ${kdump_initrd}"
	rc_status -s
	rc_failed 6
	rc_exit
    fi

    if [ -z "$KDUMP_COMMANDLINE" ]; then
	KDUMP_COMMANDLINE=`cat /proc/cmdline | \
	    sed -e 's/crashkernel=[0-9]\+[mM]\(@[0-9]\+[Mm]\?\)\?//g' \
	        -e 's/ *splash=[^ ]*/ /g' \
		-e 's/ *BOOT_IMAGE=[^ ]* / /g' \
	        -e 's/ *showopts/ /g'`
	# Use deadline for saving the memory footprint
	KDUMP_COMMANDLINE="$KDUMP_COMMANDLINE elevator=deadline sysrq=1"
	case `uname -i` in
	i?86|x86_64|ia64)
	    KDUMP_COMMANDLINE="$KDUMP_COMMANDLINE irqpoll"
            ;;
	esac
    fi
    KDUMP_COMMANDLINE="CRASH=1 $KDUMP_COMMANDLINE"

    if [ -n "$KDUMP_COMMANDLINE_APPEND" ] ; then
	KDUMP_COMMANDLINE="$KDUMP_COMMANDLINE $KDUMP_COMMANDLINE_APPEND"
    fi

    if [ -n "$KDUMP_RUNLEVEL" ]; then
	case "$KDUMP_RUNLEVEL" in
	[1-5s])
	    KDUMP_COMMANDLINE="$KDUMP_COMMANDLINE $KDUMP_RUNLEVEL"
	    ;;
	*)
	    echo " : Invalid KDUMP_RUNLEVEL=$KDUMP_RUNLEVEL, ignored"
	    ;;
	esac
    fi

    # add the dump device
    if [ -n "$KDUMP_DUMPDEV" ] ; then
        KDUMP_COMMANDLINE="dumpdev=$KDUMP_DUMPDEV $KDUMP_COMMANDLINE"
    fi

    echo 1 > /proc/sys/kernel/panic_on_oops

    KEXEC_CALL="$KEXEC -p $kdump_kernel --append=\"$KDUMP_COMMANDLINE\""
    KEXEC_CALL="$KEXEC_CALL --initrd=$kdump_initrd $KEXEC_OPTIONS"

    if [ $(($KDUMP_VERBOSE & 1)) -gt 0 ] ; then
	logger -i -t kdump "Loading kdump kernel: $KEXEC_CALL"
    fi
    if [ $(($KDUMP_VERBOSE & 4)) -gt 0 ] ; then
	echo "Loading kdump kernel: $KEXEC_CALL"
    fi
		
    eval "$KEXEC_CALL"

    rc_status -v
}

# return success if running in a crash environemnt
is_crash_kernel ()
{
    test -f /proc/vmcore || return 1
    # FIXME: any better way to detect crash environment?
    test -n "$CRASH" && return 0
    grep -q elfcorehdr= /proc/cmdline && return 0
    return 1
}

# return success if we have a valid dump on the dump device
have_valid_dump_in_dumpdev ()
{
    if [ ! -b "$KDUMP_DUMPDEV" ] ; then
        return 1
    fi

    # return the return code from this command
    $KDUMP_HELPER -c "$KDUMP_DUMPDEV" >> /dev/null 
}

# invalidate the dump device so that it's not read on next boot
invalidate_dumpdev ()
{
    dd if=/dev/zero of=$KDUMP_DUMPDEV bs=512 count=1
}

# copy the dump from the dumpdevice to the harddisk
copy_dump_from_dumpdev ()
{
    if [ $KDUMP_KEEP_OLD_DUMPS -gt 0 ]; then
        purge_old_dumps
    fi

    dumpsize=`$KDUMP_HELPER -l "$KDUMP_DUMPDEV" | sed -e 's/Length: //g'`
    if [ -z "$dumpsize" -o "$dumpsize" = 0 ] ; then
        echo -n " Unable to retrieve the dump size"
        rc_status -s
        rc_failed
    fi

    dumpsize_mb=$(($dumpsize / 1024 / 1024))

    if [ $KDUMP_FREE_DISK_SIZE -gt 0 ]; then
        restsize=`parse_rest_size "$KDUMP_SAVEDIR"`
        needsize=`expr $dumpsize_mb + $KDUMP_FREE_DISK_SIZE`
        if [ $restsize -lt $needsize ]; then
            echo -n " No enough space left on dump device ($restsize MB)"
            rc_status -s
            rc_failed 6
            return
        fi
    fi

    coredir="${KDUMP_SAVEDIR}/`date +"%Y-%m-%d-%H:%M"`"
    mkdir -p $coredir
    echo -n "Saving crash dump to $coredir"

    BS=1024
    dd if=$KDUMP_DUMPDEV of=$coredir/vmcore count=$[$dumpsize/$BS] bs=$BS
    if [ $[$dumpsize % $BS] != 0 ] ; then
        dd if=$KDUMP_DUMPDEV of=$coredir/vmcore skip=$[$dumpsize/$BS*$BS] \
                seek=$[$dumpsize/$BS*$BS] count=$[$dumpsize%$BS] bs=1
    fi
    invalidate_dumpdev
}

case "$1" in
  start)
	if is_crash_kernel; then
            if [ -z "$KDUMP_DUMPDEV" ] ; then
                if [ -n "$KDUMP_TRANSFER" ]; then
                    $KDUMP_TRANSFER
                else
                    save_core
                fi
            fi
	    if test "$KDUMP_IMMEDIATE_REBOOT" = "yes"; then
		/sbin/reboot
		# sleep to avoid the conflict with script "single"
		sleep 600
	    fi
        else
            if have_valid_dump_in_dumpdev ; then
                copy_dump_from_dumpdev
            fi
	    load_kdump
	fi
	;;
  stop)
	if ! is_crash_kernel ; then
	    if [ "$RUNLEVEL" != "" ]; then
		echo -n "Not unloading kdump during runlevel changes"
		rc_status -s
	    else
		echo -n "Unloading kdump"
		$KEXEC -p -u
		rc_status -v
	    fi
	fi
	;;
  status)
	echo "not implemented"
	;;
  restart|reload)
	$0 stop
	$0 start
	;;
  condrestart)
	;;
  *)
	echo $"Usage: $0 {start|stop|status|restart|reload}"
	exit 1
esac

exit $?

# vim: set ts=8 sw=4 sts=4 noet:
