#!/bin/sh
#
# Insert a list of installed kernels in a grub menu.lst file
#   Copyright 2001 Wichert Akkerman <wichert@linux.com>
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Contributors:
#	Jason Thomas <jason@debian.org>
#	David B.Harris <dbarclay10@yahoo.ca>
#	Marc Haber <mh@zugschlus.de>
#	Crispin Flowerday <crispin@zeus.com>

## StartOPTIONS
# name of file menu is stored in
menufile="menu.lst"

# directory's to look for the grub installation and the menu file
grubdirs="/boot/grub /boot/boot/grub"

# Default kernel options, overidden by the kopt statement in the menufile.
kopt="root=/dev/hda1 ro"

# Drive(in GRUB terms) where the kernel is located. If a seperate
# partition, this would be mounted under /boot, overridden by the kopt statement in menufile
groot="(hd0,0)"

# should grub create the alternative boot options in the menu
alternative="true"

# should grub lock the alternative boot options in the menu
lockalternative="false"

# options to use with the alternative boot options
altoptions="(recovery mode) single"

# controls howmany kernels are listed in the menu.lst,
# this does not include the alternative kernels
howmany="all"

# Default options to use in a new menu.lst . This will only be used if menu.lst
# doesn't already exist. Only edit the lines between the two "EOF"s. The others are
# part of the script.
newtemplate=$(tempfile)
cat > "$newtemplate" <<EOF
# /boot/grub/menu.lst - See: grub(8), info grub, update-grub(8)
# --------------------       grub-install(8), grub-floppy(8),
#                            grub-md5-crypt, /usr/share/doc/grub
#                            and /usr/share/doc/grub-doc/.

## default num
# Set the default entry to the entry number NUM. Numbering starts from 0, and
# the entry number 0 is the default if the command is not used.
#
# You can specify 'saved' instead of a number. In this case, the default entry
# is the entry saved with the command 'savedefault'.           
default		0

## timeout sec
# Set a timeout, in SEC seconds, before automatically booting the default entry
# (normally the first entry defined).
timeout		5

# Pretty colours
color cyan/blue white/blue

## password ['--md5'] passwd
# If used in the first section of a menu file, disable all interactive editing
# control (menu entry editor and command-line)  and entries protected by the
# command 'lock'
# e.g. password topsecret
#      password --md5 \$1\$gLhU0/\$aW78kHK1QfV3P2b2znUoe/
# password topsecret

#
# examples
#
# title		Windows 95/98/NT/2000
# root		(hd0,0)
# makeactive
# chainloader	+1
#
# title		Linux
# root		(hd0,1)
# kernel	/vmlinuz root=/dev/hda2 ro
#

#
# Put static boot stanzas before and/or after AUTOMAGIC KERNEL LIST

EOF
## End OPTIONS

# Make sure we use the standard sorting order
LC_COLLATE=C
# Magic markers we use
start="### BEGIN AUTOMAGIC KERNELS LIST"
end="### END DEBIAN AUTOMAGIC KERNELS LIST"

startopt="## ## Start Default Options ##"
endopt="## ## End Default Options ##"

# Abort on errors
set -e

abort() {
	message=$@

	echo >&2
	echo "$message" >&2
	echo >&2
	exit 1
}

# Extract options from menu.lst
ExtractMenuOpt()
{
	opt=$1

	sed -ne "/^$start\$/,/^$end\$/ {
		/^$startopt\$/,/^$endopt\$/ {
			/^# $opt=/ {
				s/^# $opt=\(.*\)\$/\1/
				p
			}
		}
	}" $menu
}

GetMenuOpt()
{
	opt=$1
	value=$2

	tmp=$(ExtractMenuOpt "$opt")

	[ -z "$tmp" ] || value="$tmp"

	echo $value
}

# Compares two version strings A and B
# Returns -1 if A<B
#          0 if A==B
#          1 if A>B
# This compares version numbers of the form
# 2.4.14-random > 2.4.14-ac10 > 2.4.14 > 2.4.14-pre2 > 
# 2.4.14-pre1 > 2.4.13-ac99
CompareVersions()
{
	# First split the version number and remove any '.' 's or dashes
	v1=$(echo $1 | sed -e 's![^0-9]\+! & !g' -e 's![\.\-]!!g')
	v2=$(echo $2 | sed -e 's![^0-9]\+! & !g' -e 's![\.\-]!!g')

	# we weight different kernel suffixes here
	# ac  - 50
	# pre - -50
	# others are given 99
	v1=$(echo $v1 | sed -e 's! ac ! 50 !g' -e 's! pre ! -50 !g' -e 's![^-0-9 ]\+!99!g')
	v2=$(echo $v2 | sed -e 's! ac ! 50 !g' -e 's! pre ! -50 !g' -e 's![^-0-9 ]\+!99!g')

	result=0; v1finished=0; v2finished=0;
	while [ $result -eq 0 -a $v1finished -eq 0 -a $v2finished -eq 0 ];
	do
		if [ "$v1" = "" ]; then
			v1comp=0; v1finished=1
		else
			set -- $v1; v1comp=$1; shift; v1=$*
		fi

		if [ "$v2" = "" ]; then
			v2comp=0; v2finished=1
		else
			set -- $v2; v2comp=$1; shift; v2=$*
		fi

		if   [ $v1comp -gt $v2comp ]; then result=1
		elif [ $v1comp -lt $v2comp ]; then result=-1
		fi
	done

	# finally return the result
	echo $result
}

# looks in the directory specified for an initrd image with the version specified
FindInitrdName()
{
	# strip trailing slashes
	directory=$(echo $1 | sed -e 's#/*$##')
	version=$2

	# initrd
	# initrd.img
	# initrd-lvm
	# .*.gz

	initrdName=""
	names="initrd initrd.img initrd-lvm"
	compressed="gz"

	for n in $names ; do
		# make sure we haven't already found it
		if [ -z "$initrdName" ] ; then
			if [ -f "$directory/$n-$version" ] ; then
				initrdName="$n-$version"
				break
			else
				for c in $compressed ; do
					if [ -f "$directory/$n-$version.$c" ] ; then
						initrdName="$n-$version.$c"
						break
					fi
				done
			fi
		else
			break
		fi
	done

	# return the result
	echo $initrdName
}

echo  -n "Searching for GRUB installation directory ... "

for d in $grubdirs ; do
	if [ -d "$d" ] ; then
		dir="$d"
		break
	fi
done

if [ -z "$dir" ] ; then
	abort "No GRUB directory found.  To create a template run 'mkdir /boot/grub' first.  To install grub, install it manually or try the 'grub-install' command.  ### Warning, grub-install is used to change your MBR. ###"
else
	echo "found: $dir ."
fi

echo -n "Testing for an existing GRUB menu.list file... "

# Test if our menu file exists
if [ -f "$dir/$menufile" ] ; then
	menu="$dir/$menufile"
	unset newtemplate
	echo "found: $menu ."
	cp -f "$dir/$menufile" "$dir/$menufile~"
else
	# if not ask user if they want us to create one
	menu="$dir/$menufile"
	echo
	echo
	echo -n "Could not find $menu file. "
	echo -n "Would you like one generated for you? "
	echo -n "(y/N) "
	read answer
	case "$answer" in
		y* | Y*)
		cat "$newtemplate" > $menu
		unset newtemplate
		;;
		*)
		abort "Not creating menu.lst as you wish"
		;;
	esac
fi

# Extract the kernel options to use
kopt=$(GetMenuOpt "kopt" "$kopt")

# Extract the grub root
groot=$(GetMenuOpt "groot" "$groot")

# Extract the old recovery value
alternative=$(GetMenuOpt "recovery" "$alternative")

# Extract the alternative value
alternative=$(GetMenuOpt "alternative" "$alternative")

# Extract the lockalternative value
lockalternative=$(GetMenuOpt "lockalternative" "$lockalternative")

# Extract the howmany value
howmany=$(GetMenuOpt "howmany" "$howmany")

# Generate the menu options we want to insert
buffer=$(tempfile)
echo $start >> $buffer
echo "## lines between the AUTOMAGIC KERNELS LIST markers will be modified" >> $buffer
echo "## by the debian update-grub script except for the default optons below" >> $buffer
echo >> $buffer
echo "## DO NOT UNCOMMENT THEM, Just edit them to your needs" >> $buffer
echo >> $buffer
echo "## ## Start Default Options ##" >> $buffer

echo "## default kernel options" >> $buffer
echo "## default kernel options for automagic boot options" >> $buffer
echo "## e.g. kopt=root=/dev/hda1 ro" >> $buffer
echo "# kopt=$kopt" >> $buffer
echo >> $buffer

echo "## default grub root device" >> $buffer
echo "## e.g. groot=(hd0,0)" >> $buffer
echo "# groot=$groot" >> $buffer
echo >> $buffer

echo "## should update-grub create alternative automagic boot options" >> $buffer
echo "## e.g. alternative=true" >> $buffer
echo "##      alternative=false" >> $buffer
echo "# alternative=$alternative" >> $buffer
echo >> $buffer

echo "## should update-grub lock alternative automagic boot options" >> $buffer
echo "## e.g. lockalternative=true" >> $buffer
echo "##      lockalternative=false" >> $buffer
echo "# lockalternative=$lockalternative" >> $buffer
echo >> $buffer

echo "## altoption boot targets option" >> $buffer
echo "## multiple altoptions lines are allowed" >> $buffer
echo "## e.g. altoptions=(extra menu suffix) extra boot options" >> $buffer
echo "##      altoptions=(recovery mode) single" >> $buffer

if ! grep -q "^# altoptions" $menu ; then
	echo "# altoptions=$altoptions" >> $buffer
else
	grep "^# altoptions" $menu >> $buffer
fi
echo >> $buffer

echo "## controls how many kernels should be put into the menu.lst" >> $buffer
echo "## only counts the first occurence of a kernel, not the" >> $buffer
echo "## alternative kernel options" >> $buffer
echo "## e.g. howmany=all" >> $buffer
echo "##      howmany=7" >> $buffer
echo "# howmany=$howmany" >> $buffer
echo >> $buffer

echo "## ## End Default Options ##" >> $buffer
echo >> $buffer

sortedKernels=""
for kern in $(/bin/ls -1vr /boot/vmlinuz-*) ; do
	# found a kernel
	newerKernels=""
	for i in $sortedKernels ; do
		res=$(CompareVersions "$kern" "$i")
		if [ "$kern" != "" -a $res -gt 0 ] ; then
			newerKernels="$newerKernels $kern $i"
			kern=""
		else
			newerKernels="$newerKernels $i"
		fi
	done
	if [ "$kern" != "" ] ; then
		newerKernels="$newerKernels $kern"
	fi
	sortedKernels="$newerKernels"
done

counter=0
for kern in $sortedKernels ; do
	counter=$(($counter + 1))
	if test ! x"$howmany" = x"all" ; then
		if [ $counter -gt $howmany ] ; then 
			break
		fi
	fi
	kernelName=$(basename $kern)
	kernelVersion=$(echo $kernelName | sed -e 's/vmlinuz-//')
	initrdName=$(FindInitrdName "/boot" "$kernelVersion")

	if mount | grep -qs "on /boot " > /dev/null 2>&1 ; then
		kernel=/$kernelName
		if [ -n "$initrdName" ] ; then
			initrd=/$initrdName
		fi
	else
		kernel=/boot/$kernelName
		if [ -n "$initrdName" ] ; then
			initrd=/boot/$initrdName
		fi
	fi

	echo "title		Debian GNU/Linux, kernel $kernelVersion" >> $buffer
	echo "root		$groot" >> $buffer
	echo "kernel		$kernel $kopt"  >> $buffer
	if [ -n "$initrdName" ]; then
		echo "initrd		$initrd" >> $buffer
	fi
	echo "savedefault" >> $buffer
	echo >> $buffer

	# insert the alternative boot options
	if test ! x"$alternative" = x"false" ; then
		# for each altoptions line do this stuff
		sed -ne 's/# altoptions=\(.*\)/\1/p' $buffer | while read line; do
			descr=$(echo $line | sed -ne 's/\(([^)]*)\)[[:space:]]\(.*\)/\1/p')
			suffix=$(echo $line | sed -ne 's/\(([^)]*)\)[[:space:]]\(.*\)/\2/p')
			echo "title		Debian GNU/Linux, kernel $kernelVersion $descr" >> $buffer
			# lock the alternative options
			if test x"$lockalternative" = x"true" ; then
				echo "lock" >> $buffer
			fi
			echo "root		$groot" >> $buffer
			echo "kernel		$kernel $kopt $suffix"  >> $buffer
			if [ -n "$initrdName" ]; then
				echo "initrd		$initrd" >> $buffer
			fi
			echo "savedefault" >> $buffer
			echo >> $buffer
		done
	fi
done

echo $end >> $buffer

echo -n "Updating $menu ... "
# Insert the new options into the menu
if ! grep -q "^$start" $menu ; then
	cat $buffer >> $menu
else
	umask 077
	sed -e "/^$start/,/^$end/{
	/^$start/r $buffer
	d
	}
	" $menu > $menu.new
	cat $menu.new > $menu
	rm -f $buffer $menu.new
fi
echo "done"
echo

if [ ! -z $answer ]; then
cat <<EOF

Please note that configuration parameters for GRUB are stored in
$menu . You must edit this file in order to set the options
which GRUB passes to the kernel, as well as the drive which GRUB looks in to
for the kernel.

Everything on the line after "kopt=" is passed to the kernel as parameters,
and "groot=" must be set to the partition(in GRUB terms, such as "(hd0,0)")
which GRUB will load the kernel from.

After you have edited $menu , please re-run 'update-grub'.
EOF
fi
