#! /bin/sh

set -e
. /usr/share/debconf/confmodule
#set -x

if [ "$1" ]; then
	ROOT="$1"
	chroot=chroot
else
	ROOT=
	chroot=
fi

. /usr/share/grub-installer/functions.sh
. /usr/share/grub-installer/otheros.sh

newline="
"

db_capb backup

log() {
	logger -t grub-installer "$@"
}

error() {
	log "error: $@"
}

info() {
	log "info: $@"
}

get_serial_console() {
	local defconsole="$(sed -e 's/.*console=/console=/' /proc/cmdline)"
	if echo "${defconsole}" | grep -q console=ttyS; then
		local PORT="$(echo "${defconsole}" | sed -e 's%^console=ttyS%%' -e 's%,.*%%')"
		local SPEED="$(echo "${defconsole}" | sed -e 's%^console=ttyS[0-9]\+,%%' -e 's% .*%%')"
		local SERIAL="ttyS${PORT},${SPEED}"
		echo "console=$SERIAL"
	fi
}

grub_serial_console() {
	#$1=output of get_serial_console
	local unit=$(echo $1 | sed -e 's%^console=ttyS%%' -e 's%,.*%%')
	local speed=$(echo $1 | sed -e 's%^console=ttyS[0-9]\+,%%' -e 's%[^(0-9)].*%%')
	local parity=$(echo $1 | sed -e 's%^console=ttyS[0-9]\+,[0-9]\+%%' -e 's%[78].*%%')
	case "$parity" in 
		"n") local parity="no" ;;
		"e") local parity="even" ;;
		"o") local parity="odd" ;;
		*)   local parity="" ;;
	esac
	local word=$(echo $1 | sed -e 's%^console=ttyS[0-9]\+,[0-9]\+[oen]%%' -e 's%r%%')
	local flow=$(echo $1 | sed -e 's%^console=ttyS[0-9]\+,[0-9]\+[oen][78]%%')

	echo serial --unit=$unit --speed=$speed --word=$word --parity=$parity --stop=1
	echo terminal serial	
	}

serial="$(get_serial_console)"

# This is copied from update-grub; we've requested that it be moved
# to a utility or shell library
device_map=$ROOT/boot/grub/device.map
# Usage: convert os_device
# Convert an OS device to the corresponding GRUB drive
# This part is OS-specific
convert () {
## First, check if the device file exists
#    if test -e "$1"; then
#		:
#    else
#		echo "$1: Not found or not a block device." 1>&2
#		exit 1
#    fi

    host_os=`uname -s | tr 'A-Z' 'a-z'`

    # Break the device name into the disk part and the partition part
    case "$host_os" in
    linux*)
		tmp_disk=`echo "$1" | sed -e 's%\([sh]d[a-z]\)[0-9]*$%\1%' \
				  -e 's%\(fd[0-9]*\)$%\1%' \
				  -e 's%/part[0-9]*$%/disc%' \
				  -e 's%\(c[0-7]d[0-9]*\).*$%\1%'`
		tmp_part=`echo "$1" | sed -e 's%.*/[sh]d[a-z]\([0-9]*\)$%\1%' \
				  -e 's%.*/fd[0-9]*$%%' \
				  -e 's%.*/floppy/[0-9]*$%%' \
				  -e 's%.*/\(disc\|part\([0-9]*\)\)$%\2%' \
				  -e 's%.*c[0-7]d[0-9]*p*%%'`
	;;
    gnu*)
		tmp_disk=`echo "$1" | sed 's%\([sh]d[0-9]*\).*%\1%'`
		tmp_part=`echo "$1" | sed "s%$tmp_disk%%"` ;;
    freebsd*)
		tmp_disk=`echo "$1" | sed 's%r\{0,1\}\([saw]d[0-9]*\).*$%r\1%' \
			    | sed 's%r\{0,1\}\(da[0-9]*\).*$%r\1%'`
		tmp_part=`echo "$1" \
	    		| sed "s%.*/r\{0,1\}[saw]d[0-9]\(s[0-9]*[a-h]\)%\1%" \
       	    	| sed "s%.*/r\{0,1\}da[0-9]\(s[0-9]*[a-h]\)%\1%"`
	;;
    netbsd*)
		tmp_disk=`echo "$1" | sed 's%r\{0,1\}\([sw]d[0-9]*\).*$%r\1d%' \
	    		| sed 's%r\{0,1\}\(fd[0-9]*\).*$%r\1a%'`
		tmp_part=`echo "$1" \
	    		| sed "s%.*/r\{0,1\}[sw]d[0-9]\([abe-p]\)%\1%"`
	;;
    *)
		echo "update-grub does not support your OS yet." 1>&2
		exit 1 ;;
    esac

    # Get the drive name
    tmp_drive=`grep -v '^#' $device_map | grep "$tmp_disk *$" \
			| sed 's%.*\(([hf]d[0-9][a-g0-9,]*)\).*%\1%'`

    # If not found, print an error message and exit
    if test "x$tmp_drive" = x; then
		echo "$1 does not have any corresponding BIOS drive." 1>&2
		exit 1
    fi

    if test "x$tmp_part" != x; then
		# If a partition is specified, we need to translate it into the
		# GRUB's syntax
		case "$host_os" in
		linux*)
	    	echo "$tmp_drive" | sed "s%)$%,`expr $tmp_part - 1`)%" ;;
		gnu*)
	    	if echo $tmp_part | grep "^s" >/dev/null; then
			tmp_pc_slice=`echo $tmp_part \
		    		| sed "s%s\([0-9]*\)[a-g]*$%\1%"`
			tmp_drive=`echo "$tmp_drive" \
		    		| sed "s%)%,\`expr "$tmp_pc_slice" - 1\`)%"`
	    	fi
	    	if echo $tmp_part | grep "[a-g]$" >/dev/null; then
			tmp_bsd_partition=`echo "$tmp_part" \
		    		| sed "s%[^a-g]*\([a-g]\)$%\1%"`
			tmp_drive=`echo "$tmp_drive" \
		    		| sed "s%)%,$tmp_bsd_partition)%"`
	    	fi
	    	echo "$tmp_drive" ;;
		freebsd*)
	    	if echo $tmp_part | grep "^s" >/dev/null; then
			tmp_pc_slice=`echo $tmp_part \
		    		| sed "s%s\([0-9]*\)[a-h]*$%\1%"`
			tmp_drive=`echo "$tmp_drive" \
		    		| sed "s%)%,\`expr "$tmp_pc_slice" - 1\`)%"`
	    	fi
	    	if echo $tmp_part | grep "[a-h]$" >/dev/null; then
			tmp_bsd_partition=`echo "$tmp_part" \
		    		| sed "s%s\{0,1\}[0-9]*\([a-h]\)$%\1%"`
			tmp_drive=`echo "$tmp_drive" \
		    		| sed "s%)%,$tmp_bsd_partition)%"`
	    	fi
	    	echo "$tmp_drive" ;;
		netbsd*)
	    	if echo $tmp_part | grep "^[abe-p]$" >/dev/null; then
			tmp_bsd_partition=`echo "$tmp_part" \
		    		| sed "s%\([a-p]\)$%\1%"`
			tmp_drive=`echo "$tmp_drive" \
		    		| sed "s%)%,$tmp_bsd_partition)%"`
	    	fi
	    	echo "$tmp_drive" ;;
		esac
    else
		# If no partition is specified, just print the drive name
		echo "$tmp_drive"
    fi
}

# Convert a linux non-devfs disk device name into the hurd's syntax
hurd_convert () {
	dr_type=$(expr "$1" : '.*\([hs]d\)[a-h][0-9]*')
	dr_letter=$(expr "$1" : '.*d\([a-h]\)[0-9]*')
	dr_part=$(expr "$1" : '.*d[a-h]\([0-9]*\)')
	case "$dr_letter" in
	a) dr_num=0 ;;
	b) dr_num=1 ;;
	c) dr_num=2 ;;
	d) dr_num=3 ;;
	e) dr_num=4 ;;
	f) dr_num=5 ;;
	g) dr_num=6 ;;
	h) dr_num=7 ;;
	esac
	echo "$dr_type${dr_num}s$dr_part"
}

# Run update-grub in $ROOT
update_grub () {
	if ! log-output -t grub-installer $chroot $ROOT update-grub -y ; then
		error "Running 'update-grub -y' failed." 1>&2
		db_input critical grub-installer/update-grub-failed || [ $? -eq 30 ]
		db_go || true
		db_progress STOP
		exit 1
	fi
}

findfstype () {
	mount | grep "on $ROOT${1%/} " | cut -d' ' -f5
}

bootfstype=$(findfstype /boot)
[ -n "$bootfstype" ] || bootfstype="$(findfstype /)"

# GRUB Legacy defaults
grub_version="grub"
menu_file="menu.lst"

# reiserfs is the only filesystem that d-i supports and grub2 doesn't yet
##if [ "$bootfstype" != "reiserfs" ]; then
##  db_input low grub-installer/grub2_instead_of_grub_legacy || [ $? -eq 30 ]
##  db_go || true
##  db_get grub-installer/grub2_instead_of_grub_legacy
##  if [ "$RET" = true ]; then
##    grub_version="grub2"
##    menu_file="grub.cfg"
##  fi
##fi

if [ "$bootfstype" = "xfs" ]; then
	# Warn user that grub on xfs is not safe and let them back out to
	# main menu
	db_input critical grub-installer/install_to_xfs || [ $? -eq 30 ]
	db_go || exit 10
	db_get grub-installer/install_to_xfs
	if [ "$RET" != true ]; then
		exit 10
	fi
fi

db_progress START 0 6 grub-installer/progress/title

db_progress INFO grub-installer/progress/step_install

# apt-install passes --no-remove to apt, but grub{,2} conflict each other, so
# we need to purge them first to support users who try grub2 and then switch
# to grub legacy, or vice-versa
##log-output -t grub-installer $chroot $ROOT dpkg -P grub grub2

if ! apt-install $grub_version ; then
	info "Calling 'apt-install $grub_version' failed"
	# Hm, unable to install grub into $ROOT/, what should we do?
	db_input critical grub-installer/apt-install-failed || [ $? -eq 30 ]
	if ! db_go; then
		db_progress STOP
		exit 10 # back up to menu
	fi
	db_get grub-installer/apt-install-failed
	if [ true != "$RET" ] ; then
		db_progress STOP
		exit 1
	fi
fi

db_progress STEP 1
db_progress INFO grub-installer/progress/step_os-probe
os-prober > /tmp/os-probed || true

# Work out what probed OSes can be booted from grub
tmpfile=/tmp/menu.lst.extras
if [ -s /tmp/os-probed ]; then
	supported_os_list=""
	unsupported_os_list=""

	OLDIFS="$IFS"
	IFS="$newline"
	for os in $(cat /tmp/os-probed); do
		IFS="$OLDIFS"
		title=$(echo "$os" | cut -d: -f2)
		type=$(echo "$os" | cut -d: -f4)
		case "$type" in
			chain)
				:
			;;
			linux)
				# Check for linux systems that we don't
				# know how to boot
				partition=$(echo "$os" | cut -d: -f1)
				if [ -z "$(linux-boot-prober $partition)" ]; then
					if [ -n "$unsupported_os_list" ]; then
						unsupported_os_list="$unsupported_os_list, $title"
					else
						unsupported_os_list="$title"
					fi
					continue
				fi
			;;
			hurd)
				:
			;;
			*)
				if [ -n "$unsupported_os_list" ]; then
					unsupported_os_list="$unsupported_os_list, $title"
				else
					unsupported_os_list="$title"
				fi
				continue
			;;
		esac

		if [ -n "$supported_os_list" ]; then
			supported_os_list="$supported_os_list, $title"
		else
			supported_os_list="$title"
		fi
		
		IFS="$newline"
	done
	IFS="$OLDIFS"
	
	if [ -n "$unsupported_os_list" ]; then
		# Unsupported OS, jump straight to manual boot device question.
		state=2
	else
		q=grub-installer/with_other_os
		db_subst $q OS_LIST "$supported_os_list"
		state=1
	fi
else
	q=grub-installer/only_debian
	state=1
fi

db_progress STEP 1
db_progress INFO grub-installer/progress/step_bootdev

while : ; do
	if [ "$state" = 1 ]; then
		db_input high $q || true
		if ! db_go; then
			# back up to menu
			db_progress STOP
			exit 10
		fi
		db_get $q
		if [ "$RET" = true ]; then
			bootdev="(hd0)"
			break
		else
			state=2
		fi
	else
		db_input critical grub-installer/bootdev || true
		if ! db_go; then
			if [ "$q" ]; then
				state=1
			else
				# back up to menu
				db_progress STOP
				exit 10
			fi
		else
			db_get grub-installer/bootdev
			bootdev=$RET
			if echo "$bootdev" | grep -qv '('; then
				mappedbootdev=$(mapdevfs "$bootdev") || true
				if [ -n "$mappedbootdev" ]; then
					bootdev="$mappedbootdev"
				fi
			fi
			break
		fi
	fi
done

db_progress STEP 1
db_subst grub-installer/progress/step_install_loader BOOTDEV "$bootdev"
db_progress INFO grub-installer/progress/step_install_loader

info "Installing grub on '$bootdev'"

update_mtab

# Install grub on each space separated disk in the list
bootdevs="$bootdev"
for bootdev in $bootdevs; do
	if ! is_floppy "$bootdev"; then
		if $chroot $ROOT grub-install -h 2>&1 | grep -q no-floppy; then
			info "grub-install supports --no-floppy"
			floppyparam="--no-floppy"
		else
			info "grub-install does not support --no-floppy"
		fi
	fi

	info "Running $chroot $ROOT grub-install --recheck $floppyparam \"$bootdev\""
	if log-output -t grub-installer $chroot $ROOT grub-install --recheck $floppyparam "$bootdev"; then
		info "grub-install ran successfully"
	else
		error "Running 'grub-install --recheck $floppyparam \"$bootdev\"' failed."
		db_subst grub-installer/grub-install-failed BOOTDEV "$bootdev"
		db_input critical grub-installer/grub-install-failed || [ $? -eq 30 ]
		db_go || true
		db_progress STOP
		exit 1
	fi
done

db_progress STEP 1
db_progress INFO grub-installer/progress/step_config_loader

# Delete for idempotency
rm -f $ROOT/boot/grub/$menu_file
update_grub

# Set up a password if asked
db_input low grub-installer/password || true
if ! db_go; then
	# back up to menu
	db_progress STOP
	exit 10
fi
db_get grub-installer/password
if [ "$RET" ]; then
	password="$RET"
	# check if the password is crypted
	db_get grub-installer/password-crypted
	if [ "$RET" = true ]; then
		password_opts=--md5
	fi
	echo "password ${password_opts:+$password_opts }$password" \
		> /tmp/menu.lst.password
	# Add a line to menu.lst to use the given password
	# The line is appended after the commented example
	sed -i '/^# password/r /tmp/menu.lst.password' $ROOT/boot/grub/$menu_file
	# By default, menu.lst is world-readable, which is not so good if it
	# contains a password.
	chmod o-r $ROOT/boot/grub/$menu_file
	rm -f /tmp/menu.lst.password
fi 

user_params=$(echo $(user-params)) || true
if [ "$user_params" ]; then
	# Modify menu.lst to include user parameters.
	sed -i "s!^\(# kopt=.*\)!\1 $user_params!" $ROOT/boot/grub/$menu_file
	update_grub # again, to add new options to all the Debian kernel entries
fi

if [ "$serial" ] ; then
	# Modify menu.lst so _grub_ uses serial console.
	(grub_serial_console $serial; cat $ROOT/boot/grub/$menu_file) >$ROOT/boot/grub/$menu_file.new
	mv $ROOT/boot/grub/$menu_file.new $ROOT/boot/grub/$menu_file
fi 

# Generate menu.lst additions for other OSes
tmpfile=/tmp/menu.lst.extras
OLDIFS="$IFS"
IFS="$newline"
for os in $(cat /tmp/os-probed); do
	IFS="$OLDIFS"
	title=$(echo "$os" | cut -d: -f2)
	shortname=$(echo "$os" | cut -d: -f3)
	type=$(echo "$os" | cut -d: -f4)
	case "$type" in
		chain)
			partition=$(mapdevfs $(echo "$os" | cut -d: -f1))
			grubdrive=$(convert "$partition") || true
			if [ -n "$grubdrive" ]; then
				case $grub_version in
				    grub)	grub_write_chain ;;
				    grub2)	grub2_write_chain ;;
				esac
			fi
		;;
		linux)
			partition=$(echo "$os" | cut -d: -f1)
			mappedpartition=$(mapdevfs "$partition")
			IFS="$newline"
			for entry in $(linux-boot-prober "$partition"); do
				IFS="$OLDIFS"
				bootpart=$(echo "$entry" | cut -d: -f2)
				mappedbootpart=$(mapdevfs "$bootpart") || true
				if [ -z "$mappedbootpart" ]; then
					mappedbootpart="$bootpart"
				fi
				label=$(echo "$entry" | cut -d : -f3)
				if [ -z "$label" ]; then
					label="$title"
				fi
				kernel=$(echo "$entry" | cut -d : -f4)
				initrd=$(echo "$entry" | cut -d : -f5)
				if echo "$kernel" | grep -q '^/boot/' && \
				   [ "$mappedbootpart" != "$mappedpartition" ]; then
				   	# separate /boot partition
					kernel=$(echo "$kernel" | sed 's!^/boot!!')
					initrd=$(echo "$initrd" | sed 's!^/boot!!')
					grubdrive=$(convert "$mappedbootpart") || true
				else
					grubdrive=$(convert "$mappedpartition") || true
				fi
				params="$(echo "$entry" | cut -d : -f6-) $serial"
				case $grub_version in
				    grub)	grub_write_linux ;;
				    grub2)	grub2_write_linux ;;
				esac
				IFS="$newline"
			done
			IFS="$OLDIFS"
		;;
		hurd)
			partition=$(mapdevfs $(echo "$os" | cut -d: -f1))
			grubdrive=$(convert "$partition") || true
			hurddrive=$(hurd_convert "$partition") || true
			# Use the standard hurd boilerplate to boot it.
			case $grub_version in
			    grub)	grub_write_hurd ;;
			    grub2)	grub2_write_hurd ;;
			esac
		;;
		*)
			info "unhandled: $os"
		;;
	esac
	IFS="$newline"
done
IFS="$OLDIFS"
rm -f /tmp/os-probed

if [ -s $tmpfile ] ; then
	case $grub_version in
	    grub)	grub_write_divider ;;
	    grub2)	: ;;
	esac
	cat $tmpfile >> $ROOT/boot/grub/$menu_file
	rm -f $tmpfile
fi

db_progress STEP 1
db_progress INFO grub-installer/progress/step_update_etc

sed -i 's/do_bootloader = yes/do_bootloader = no/' $ROOT/etc/kernel-img.conf
if [ -z "$(grep update-grub $ROOT/etc/kernel-img.conf)" ]; then
	(
		echo "postinst_hook = update-grub"
		echo "postrm_hook   = update-grub"
	) >> $ROOT/etc/kernel-img.conf
fi

db_progress STEP 1
db_progress STOP
