#!/bin/bash
set -e -o pipefail
. /etc/functions

TRACE_FUNC

bootdir="$1"
file="$2"

if [ -z "$bootdir" -o -z "$file" ]; then
	die "Usage: $0 /boot /boot/grub/grub.cfg"
fi

reset_entry() {
	name=""
	kexectype="elf"
	kernel=""
	initrd=""
	modules=""
	append=""
}

filedir=`dirname $file`
DEBUG "filedir= $filedir"
bootdir="${bootdir%%/}"
DEBUG "bootdir= $bootdir"
bootlen="${#bootdir}"
DEBUG "bootlen= $bootlen"
appenddir="${filedir:$bootlen}"
DEBUG "appenddir= $appenddir"

fix_path() {
	path="$@"
	if [ "${path:0:1}" != "/" ]; then
		DEBUG "fix_path: path was $@"
		path="$appenddir/$path"
		DEBUG "fix_path: path is now $path"
	fi
}

# GRUB kernel lines (linux/multiboot) can include a command line.  Check whether
# the file path exists in $bootdir.
check_path() {
	local checkpath firstval
	checkpath="$1"
	firstval="$(echo "$checkpath" | cut -d\  -f1)"
	if ! [ -r "$bootdir$firstval" ]; then
		DEBUG "$bootdir$firstval doesn't exist"
		return 1; 
	fi
	return 0
}

echo_entry() {
	if [ -z "$kernel" ]; then return; fi

	fix_path $kernel
	# The kernel must exist - if it doesn't, ignore this entry, it
	# wouldn't work anyway.  This could happen if there was a
	# GRUB variable in the kernel path, etc.
	if ! check_path "$path"; then return; fi
	entry="$name|$kexectype|kernel $path"

	case "$kexectype" in
		elf)
			if [ -n "$initrd" ]; then
				for init in $(echo $initrd | tr ',' ' '); do
					fix_path $init
					# The initrd must also exist
					if ! check_path "$path"; then return; fi
					entry="$entry|initrd $path"
				done
			fi
			if [ -n "$append" ]; then
				entry="$entry|append $append"
			fi
			;;
		multiboot|xen)
			entry="$entry$modules"
			;;
		*)
			return
			;;
	esac

	# Double-expand here in case there are variables in the kernel
	# parameters - some configs do this and can boot with empty
	# expansions (Debian Live ISOs use this for loopback boots)
	echo $(eval "echo \"$entry\"")
}

search_entry() {
	case $line in
		menuentry* | MENUENTRY* )
			state="grub"
			reset_entry
			name=`echo $line | tr "'" "\"" | cut -d\" -f 2`
			;;

		label* | LABEL* )
			state="syslinux"
			reset_entry
			name=`echo $line | cut -c6- `
	esac
}

grub_entry() {
	if [ "$line" = "}" ]; then
		echo_entry
		state="search"
		return
	fi

	# add info to menuentry
	trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
	cmd=`echo $trimcmd | cut -d\  -f1`
	val=`echo $trimcmd | cut -d\  -f2-`
	case $cmd in
		multiboot*)
			# TODO: differentiate between Xen and other multiboot kernels
			kexectype="xen"
			kernel="$val"
			DEBUG " grub_entry multiboot kernel= $kernel"
			;;
		module*)
			case $val in
				--nounzip*) val=`echo $val | cut -d\  -f2-` ;;
			esac
			fix_path $val
			modules="$modules|module $path"
			DEBUG " grub_entry linux modules= $modules"
			;;
		linux*)
			# Some configs have a device specification in the kernel
			# or initrd path.  Assume this would be /boot and remove
			# it.  Keep the '/' following the device, since this
			# path is relative to the device root, not the config
			# location.
			DEBUG " grub_entry : linux trimcmd prior of kernel/append parsing: $trimcmd"
			kernel=`echo $trimcmd | sed "s/([^)]*)//g" | cut -d\  -f2`
			append=`echo $trimcmd | cut -d\  -f3-`
			;;
		initrd*)
			# Trim off device specification as above
			initrd="$(echo "$val" | sed "s/([^)]*)//g")"
			DEBUG " grub_entry: linux initrd= $initrd"
			;;
	esac
}

syslinux_end() {
	# finish menuentry

	# attempt to parse out of append if missing initrd
	if [ -z "$initrd" ]; then
		newappend=""
		for param in $append; do
			case $param in
				initrd=*)
					initrd=`echo $param | cut -d\= -f2`
					;;
				*) newappend="$newappend $param" ;;
			esac
		done
		append="${newappend##' '}"
	fi

	appenddir="$(echo $appenddir | cut -d\/ -f -2)"
	echo_entry
	state="search"
}

syslinux_multiboot_append() {
	splitval=`echo "${val// --- /|}" | tr '|' '\n'`
	while read line
	do
		if [ -z "$kernel" ]; then
			kernel="$line"
		else
			fix_path $line
			modules="$modules|module $path"
		fi
	done << EOF
$splitval
EOF
}

syslinux_entry() {
	case $line in
		"")
			syslinux_end
			return
			;;
		label* | LABEL* )
			syslinux_end
			search_entry
			return
			;;
	esac

	# add info to menuentry
	trimcmd=`echo $line | tr '\t ' ' ' | tr -s ' '`
	cmd=`echo $trimcmd | cut -d\  -f1`
	val=`echo $trimcmd | cut -d\  -f2-`
	case $trimcmd in
		menu* | MENU* )
			cmd2=`echo $trimcmd | cut -d \  -f2`
			if [ "$cmd2" = "label" -o "$cmd2" = "LABEL" ]; then
				name=`echo $trimcmd | cut -c11- | tr -d '^'`
			fi
			;;
		linux* | LINUX* | kernel* | KERNEL* )
			case $val in
				# TODO: differentiate between Xen and other multiboot kernels
				*mboot.c32) kexectype="xen" ;;
				*.c32)
					# skip this entry
					state="search"
					;;
				*)
					kernel="$val"
					DEBUG "kernel= $kernel"
			esac
			;;
		initrd* | INITRD* )
			initrd="$val"
			DEBUG "initrd= $initrd"
			;;
		append* | APPEND* )
			if [ "$kexectype" = "multiboot" -o "$kexectype" = "xen" ]; then
				syslinux_multiboot_append
			else
				append="$val"
				DEBUG "append= $append"
			fi
			;;
	esac
}

state="search"
while read line
do
	case $state in
		search)
			search_entry
			;;
		grub)
			grub_entry
			;;
		syslinux)
			syslinux_entry
			;;
	esac
done < "$file"

# handle EOF case
if [ "$state" = "syslinux" ]; then
	syslinux_end
fi
