#!/bin/bash
#
# Stefan Seyfried, SUSE Linux Products GmbH 2006, GPL v2
# mostly taken from the powersave project.

GRUBONCE="/usr/sbin/grubonce"
GRUBDEFAULT="/boot/grub/default"
GRUBDEFSAVE="/run/suspend.grubonce.default"

#####################################################################
# gets a list of available kernels from /boot/grub/menu.lst
# kernels are in the array $KERNELS, output to stdout to be eval-ed.
getkernels()
{
	# DEBUG "Running getkernels()" INFO
	local MENU_LST="/boot/grub/menu.lst"
	local I DUMMY MNT ROOTDEV
	declare -i I=0 J=-1
	
	# we need the root partition later to decide if this is the kernel to select
	while read ROOTDEV MNT DUMMY; do
		[ "$ROOTDEV" = "rootfs" ] && continue # not what we are searching for
		if [ "$MNT" = "/" ]; then
			break
		fi
	done < /proc/mounts
	
	# build an array KERNELS with all the kernels in /boot/grub/menu.lst
	# the array MENU_ENTRIES contains the corresponding menu entry numbers
	# DEFAULT_BOOT contains the default entry.
	while read LINE; do
		case $LINE in
		title*)
			let J++ # increase for every menu entry, even for non-linux
			# DEBUG "Found grub menu entry #${J}: '${LINE}'" INFO
			;;
		default*)
			DUMMY=($LINE)                   # "default 0 #maybe a comment"
			echo "DEFAULT_BOOT=${DUMMY[1]}" #  ^^[0]^^ 1 ^^[2]^ 3 ^^[4]^^
			# DEBUG "Default boot entry is '${DUMMY[1]}'" INFO
			;;
		kernel*noresume*)
			# we probably found the "failsafe" kernel that won't resume...
			echo "  Skipping grub entry #${J}, because it has the noresume option" >&2
			;;
		kernel*root=*)
			local ROOT
			ROOT=${LINE#*root=}
			DUMMY=($ROOT)
			ROOT=${DUMMY[0]}
			if [ "$(echo $ROOT | grep LABEL)" ]; then
				LABEL=`echo $ROOT | cut -d"=" -f2`
				ROOT=`readlink -f /dev/disk/by-label/$LABEL`
			elif [ "$(echo $ROOT | grep UUID)" ]; then
				UUID=`echo $ROOT | cut -d"=" -f2`
				ROOT=`readlink -f /dev/disk/by-uuid/$UUID`
			fi
			if [ "$(stat -Lc '%t:%T' $ROOT)" != "$(stat -Lc '%t:%T' $ROOTDEV)" ]; then
				echo "  Skipping grub entry #${J}, because its root= parameter ($ROOT)" >&2
				echo "    does not match the current root device ($ROOTDEV)." >&2
				continue
			fi
			DUMMY=($LINE) # kernel (hd0,1)/boot/latestkernel-ABC root=/dev/hda2
			echo "KERNELS[$I]='${DUMMY[1]##*/}'" # latestkernel-ABC
			echo "MENU_ENTRIES[$I]=$J"
			# DEBUG "Found kernel entry #${I}: '${DUMMY[1]##*/}'" INFO
			let I++
			;;
		kernel*)
			# a kernel without "root="? We better skip that one...
			echo "  Skipping grub entry #${J}, because it has no root= option" >&2
			;;
		*)  ;;
		esac
	done < $MENU_LST
}

#############################################################
# runs grubonce from the grub package to select which kernel
# to boot on next startup
grub-once()
{
	if [ -x "$GRUBONCE" ]; then
		rm -f "$GRUBDEFSAVE"
		if [ -e "$GRUBDEFAULT" ]; then
			echo "  saving original $GRUBDEFAULT"
			cp "$GRUBDEFAULT" "$GRUBDEFSAVE"
		fi
		echo "  running '$GRUBONCE $1'"
		$GRUBONCE $1
	else
		echo "WARNING: $GRUBONCE not found, not preparing bootloader"
	fi
}

#############################################################
# restore grub default after (eventually failed) resume
grub-once-restore()
{
	echo "INFO: running grub-once-restore"
	rm -f "$GRUBDEFAULT"
	if [ -e "$GRUBDEFSAVE" ]; then
		echo "  restoring original $GRUBDEFAULT"
		mv "$GRUBDEFSAVE" "$GRUBDEFAULT"
	fi
}

#############################################################################
# try to find a kernel image that matches the actually running kernel.
# We need this, if more than one kernel is installed. This works reasonably
# well with grub, if all kernels are named "kernel-`uname -r`" and are
# located in /boot. If they are not, good luck ;-)
find-kernel-entry()
{
	NEXT_BOOT=-1
	ARCH=`uname -m`
	declare -i I=0
	# DEBUG "running kernel: $RUNNING" DIAG
	while [ -n "${KERNELS[$I]}" ]; do
		BOOTING="${KERNELS[$I]}"
		if IMAGE=`readlink /boot/$BOOTING` && [ -e "/boot/${IMAGE##*/}" ]; then
			# DEBUG "Found kernel symlink $BOOTING => $IMAGE" INFO
			BOOTING=$IMAGE
		fi
		case $ARCH in
			ppc*)   BOOTING="${BOOTING#*vmlinux-}" ;;
			*)      BOOTING="${BOOTING#*kernel-}" ;;
		esac
		if [ "$RUNNING" == "$BOOTING" ]; then
			NEXT_BOOT=${MENU_ENTRIES[$I]}
			echo "  running kernel is grub menu entry $NEXT_BOOT (${KERNELS[$I]})"
			break
		fi
		let I++
	done
	# if we have not found a kernel, issue a warning.
	# if we have found a kernel, we'll do "grub-once" later, after
	# prepare_suspend finished.
	if [ $NEXT_BOOT -eq -1 ]; then
		echo "WARNING: no kernelfile matching the running kernel found"
	fi
}

#############################################################################
# if we did not find a kernel (or BOOT_LOADER is not GRUB) check,
# if the running kernel is still the one that will (probably) be booted for
# resume (default entry in menu.lst or, if there is none, the kernel file
# /boot/latestkernel points to.)
# This will only work, if you use "original" SUSE kernels.
# you can always override with the config variable set to "yes"
prepare-grub()
{
	echo "INFO: running prepare-grub"
	eval `getkernels`
	RUNNING=`uname -r`
	find-kernel-entry

	RET=0

	if [ $NEXT_BOOT -eq -1 ]; then
		# which kernel is booted with the default entry?
		BOOTING="${KERNELS[$DEFAULT_BOOT]}"
		# if there is no default entry (no menu.lst?) we fall back to
		# the default of /boot/latestkernel.
		[ -z "$BOOTING" ] && BOOTING="latestkernel"
		if IMAGE=`readlink /boot/$BOOTING` && [ -e "/boot/${IMAGE##*/}" ]; then
			BOOTING=$IMAGE
		fi
		BOOTING="${BOOTING#*kernel-}"
		echo  "running kernel: '$RUNNING', probably booting kernel: '$BOOTING'"
		if [ "$BOOTING" != "$RUNNING" ]; then
			echo "ERROR: kernel version mismatch, cannot suspend to disk"
			echo "running: $RUNNING booting: $BOOTING"  >> $INHIBIT
			RET=1
		fi
	else
		# set the bootloader to the running kernel
		echo "  preparing boot-loader: selecting entry $NEXT_BOOT, kernel /boot/$BOOTING"
		T1=`date +"%s%N"`
		sync; sync; sync # this is needed to speed up grub-once on reiserfs
		T2=`date +"%s%N"`
		echo "  grub-once: `grub-once $NEXT_BOOT`"
		T3=`date +"%s%N"`
		S=$(((T2-T1)/100000000)); S="$((S/10)).${S:0-1}"
		G=$(((T3-T2)/100000000)); G="$((G/10)).${G:0-1}"
		echo "    time needed for sync: $S seconds, time needed for grub: $G seconds."
	fi

	return $RET
}


###### main()

case $1 in
	hibernate)
		prepare-grub
		;;
	thaw)
		grub-once-restore
		;;
esac
