#!/bin/sh
#
# altboot provides a simple bootmenu before init is beeing run.
# There are two default menu entries: "Normal boot" and "Single user mode"
# New menu entries can be created be putting files into /etc/altboot-menu.
#

test -e /etc/altboot.func && . /etc/altboot.func || die "ERROR: /etc/altboot.func not found. Check your installation!"
 
CURRENT_ENV="`set`"
VERSION="DEVELOPER SNAPSHOT"

# Set some defaults in case altboot.cfg is missing
REAL_INIT="/sbin/init.sysvinit"
ALTBOOT_LAST_FILE="/etc/altboot.last"

# Read default runlevel from inittab
INIT_RUNLEVEL="`cat /etc/inittab | sed -n "/^id\:/s/id\:\([0-9]\)\:.*$/\1/p"`"
test -z "$INIT_RUNLEVEL" && INIT_RUNLEVEL=5

# If this step fails the results are fatal. Seen on Collie / kernel 2.4 (where else...)
OUT_TTY="`tty`" ; test -z "$OUT_TTY" && OUT_TTY="/dev/tty1"

if ( echo "$OUT_TTY" | grep -q "not" )
then
	OUT_TTY="/dev/tty1"
	echo "WARNING: Assignment of OUT_TTY failed (2)!" > "$OUT_TTY"
fi

case "`uname -r`" in
2.6*)	ALTBOOT_CFG_FILE="/etc/altboot-2.6.cfg";;
2.4*)	ALTBOOT_CFG_FILE="/etc/altboot-2.4.cfg";;
*)	echo "Warning: Unknown kernel [`uname -r`], using kernel 2.6 configuration!"
	ALTBOOT_CFG_FILE="/etc/altboot-2.6.cfg";;
esac

test -e "$ALTBOOT_CFG_FILE" && . "$ALTBOOT_CFG_FILE" || echo "WARNING: No $ALTBOOT_CFG_FILE found! Check your installation of Altboot!" > "$OUT_TTY"

if test -e "${ALTBOOT_CFG_FILE}.next-reboot"
then
	. "${ALTBOOT_CFG_FILE}.next-reboot"
	rm "${ALTBOOT_CFG_FILE}.next-reboot"
fi

ENABLE_DEBUG="$ENABLE_DEBUGGING"

C_RED="\033[31m"
C_YELLOW="\033[33m"
C_BLUE="\033[34m"
C_WHITE="\033[37m"
C_RESET="\033[0m"


die() {
	echo -e "${C_RED}ERROR: $1${C_RESET}" >"$OUT_TTY"
	exec $SH_SHELL <"$OUT_TTY" >"$OUT_TTY" 2>&1
}

debug_shell() {		
	# VT 2 = Opie, VT 3 = GPE
	test -z "$1" && VT=4 || VT=$1
	
	echo -e "\033c" > /dev/tty$VT

	echo -en "\nPress <ENTER> to activate the debug shell." > /dev/tty$VT
	read junk </dev/tty$VT
		
	echo "" > /dev/tty$VT	
	/bin/sh  </dev/tty$VT >/dev/tty$VT 2>&1
	
	#openvt -lf -c$VT -- /bin/sh </dev/tty$VT >/dev/tty$VT 2>&1
}

# This function prints the boot-menu
# $1: Directory containing the scripts for the menu-items
show_menu() {
	test -z "$1" && die "DEBUG: Parameter 1 is empty in show_menu"
	! test -d "$1" && die "show_menu: [$1] not found or no directory."
	
	echo "" 
	echo -e "altboot v$VERSION\n" 	
	
	m_entry=""

	cd $1

	cnt=0 ; reset_pref "menu_filelist"
	for file in `ls -1`
	do	
#		debug_echo "show_menu(): file = [$file]"
		if ! test -d "$1/$file"
		then			
			# NOTE: It is important to use "." here so that the script inherits
			# the shell environment / all set variables!					
			M_TITLE="`. $1/$file title`"
			FLAGS="`$1/$file flags`"
			
			if ! test -z "$M_TITLE"
			then
				let cnt=$cnt+1
				# Keep a list of existing "modules" together with an index number
				# This sure is ugly, but Busybox sh doesn't do arrays....
				#m_entry="`echo -e "$m_entry\n$cnt:$file\n"`"
				
				set_pref "menu_filelist" "$cnt" "$file"
				test -n "$FLAGS" && set_pref "menu_fileflags" "$cnt" "$FLAGS"
				echo -e "\t\t[$cnt] $M_TITLE"					
			fi
			M_TITLE=""
		fi
	done

#	debug_echo "FILES READ"

	# Display directories below /etc/altboot-menu as menu-item
	# and add all scripts inside the directory to m_entry
	for dir in `ls -1`
	do	
		if test -d "$1/$dir"
		then			
			M_TITLE="`basename "$1/$dir"`"
			if ! test -z "$M_TITLE"
			then
				let cnt=$cnt+1
				# Keep a list of existing "modules" together with an index number
				# This sure is ugly, but Busybox sh doesn't do arrays....
				
				set_pref "menu_filelist" "$cnt" "$dir:DIR"
				
				# Set noRemember flag for directories
				set_pref "menu_fileflags" "$cnt" "noRemember"				
						
				echo -e "\t\t[$cnt] $M_TITLE"					

				OLD_PWD="$PWD"
				cd "$1/$dir"
				for file in `ls -1`
				do	
					if ! test -d "$1/$dir/$file"
					then			
						M_TITLE="`$1/$dir/$file title`"
						FLAGS="`$1/$dir/$file flags`"
						
						if ! test -z "$M_TITLE"
						then
							let cnt=$cnt+1
							# Keep a list of existing "modules" together with an index number
							# This sure is ugly, but Busybox sh doesn't do arrays....
							
							set_pref "menu_filelist" "$cnt" "$dir/$file"
							test -n "$FLAGS" && set_pref "menu_fileflags" "$cnt" "$FLAGS"				
						fi
						M_TITLE=""
					fi
				done
				cd "$OLD_PWD"

			else 
				debug_echo "show_menu(): \$M_TITLE is empty"
			fi
			M_TITLE=""
		fi
	done

#	debug_echo "DIRS READ"

	echo ""		
}

# This function is used to display the content of directories below
# /etc/altboot-menu as menu-items
show_sub_menu() {
	dirname="`basename "$1"`"
		
	d_entries="`dump_pref "menu_filelist" | grep "$dirname/"`"
		
	echo -e "\naltboot v$VERSION: $dirname menu\n" 
		
	for d_entry in $d_entries
	do
		d_entry_number="`echo "$d_entry"| sed -n "s/\(.*\)\#\#\(.*\)\#\#\#/\1/p"`"
		d_entry_file="`echo "$d_entry"| sed -n "s/\(.*\)\#\#\(.*\)\#\#\#/\2/p"`"
		d_entry_title="`$d_entry_file title`"
				
		echo -e "\t\t[$d_entry_number] $d_entry_title"
	done
		
	echo ""
	
}

get_kbd_ints(){
	if ( uname -r | grep -q ^2.6 )
	then				
		if test -z "$KBD_INT"
		then		
			# find out how the keyboard is called
			for kbd in Spitzkbd corgikbd locomokbd tosakbd "Neo1973 AUX button"
			do
				if ( cat /proc/interrupts | grep -q "$kbd" )
				then
					debug_echo "run_timer(): Using [$kbd] as keyboard interrupt"
					KBD_INT="$kbd"	
					break				
				fi
			done				
		fi
		key_ints="`cat /proc/interrupts | grep "$KBD_INT"`"
		#debug_echo "run_timer(): key_ints = [$key_ints]"
	else
		KBD_INT="$kbd"
		debug_echo "\nrun_timer(): Using [keyboard] as keyboard interrupt in kernel-2.4 mode"
		key_ints="`cat /proc/interrupts | grep keyboard | awk '{print $2}'`"
	fi
	
	test -z "$KBD_INT" && debug_echo "Couldn't read keyboard ints!"	
}

# Shows the timer and sets $launch_altboot to yes if a keypress was detected
run_timer() {
	if test "$TIMEOUT" != 0 -a "$TIMEOUT" != "[none]"
	then			
			
		mount -t proc proc /proc >/dev/null 2>&1
		
		get_kbd_ints			
		kbd_ints_old="$key_ints"				
		
		#debug_echo "run_timer() old:`echo $key_ints | md5sum`"
		
		stty -echo  <"$OUT_TTY" >"$OUT_TTY" 2>&1
		
		case "$KBD_INT" in
		Neo1973*)	echo -en "\n\nPlease press [AUX] to launch altboot." > "$OUT_TTY" ;;
		*)		echo -en "\n\nPlease press any key to launch altboot." > "$OUT_TTY" ;;
		esac
		
		test -z "$TIMEOUT" && TIMEOUT="3"

		cnt=0
		while test "$cnt" != "$TIMEOUT"
		do		
			sleep 1
			
			get_kbd_ints			
			kbd_ints_new="$key_ints"				

			if test "$kbd_ints_new" != "$kbd_ints_old" -o -z "$kbd_ints_new"
			then				
				launch_altboot=yes				
				stty echo <"$OUT_TTY" >"$OUT_TTY" 2>&1
				break
			fi
			echo -n "." >"$OUT_TTY"
			let cnt=$cnt+1
		done
		
		if test "$launch_altboot" != "yes"
		then
			AUTOBOOT=yes
		else
			rm -f /etc/.altboot*.last
		fi
				
	else
		launch_altboot=yes
	fi
	
	if test "$TIMEOUT" = "[none]"
	then
		echo "" 
		echo -e "altboot v$VERSION ($MACHINE)\n" 
		
		echo "NOTE: The menu is not available on this device"
		echo "Use \"set-bootdev\" to change the boot device"
		
		launch_altboot=no
	fi
} 

# $1 = ID
parse_module_flags() {
	if test -n "$1"
	then		
		# Each module can set "flags" to influence handling of the module
		get_pref "menu_fileflags" "$1" flags
		
		test -n "$flags" && debug_echo "parse_module_flags(): [$1] has flags [$flags]"
		
		for flag in $flags
		do
			case "$flag" in
			noRemember)	REMEMBER_LAST_SELECTION=no;;
			esac
		done
	else 
		debug_echo "parse_module_flags(): Empty \$1"
	fi
}

# This function launches the selected menu item / script
# $1: Directory containing the scripts for the menu-items
launch_selection() {
	test -z "$1" && die "Parameter 1 of launch_selection is empty!"
	
	case "$junk" in
	*)	#file="`echo "$m_entry"| sed -n "/$junk\:/s/^.*\:\(.*\)/\1/p"`"
		
		get_pref "menu_filelist" "$junk" file_
		type="`echo "$file_" | sed -n "s/\(.*\)\:\(.*\)/\2/p"`"
		file="`echo "$file_" | sed -n "s/\(.*\)\:\(.*\)/\1/p"`"
		test -z "$file" && file="$file_"
				
#		debug_echo "[$file_]: [$type] : [$flags] / [$file] ($junk)"
		
		# The selected menu-item points to a directory
		if test "$type" = DIR
		then
			show_sub_menu /etc/altboot-menu/$file >"$OUT_TTY"
			wait_for_input >"$OUT_TTY"			
			launch_selection /etc/altboot-menu >"$OUT_TTY"						
		fi
		
		if test "$type" = MAIN
		then
			show_sub_menu /etc/altboot-menu >"$OUT_TTY"
			wait_for_input >"$OUT_TTY"			
			launch_selection /etc/altboot-menu >"$OUT_TTY"						
		fi	
		
		MENU_POSITION="$junk"
		
		# AUTOSTART is "1" when the timer on boot times out ($USER didn't press
		# a key)
		if test "$AUTOSTART" = "true"
		then		
#			debug_echo "launch_selection(): last_selection_data = [$last_selection_data]" >"$OUT_TTY"
			. $1/$file autorun "$file" "$last_selection_data" >"$OUT_TTY"	
		else
			. $1/$file run "$file" >"$OUT_TTY"
		fi
			
		test "$OFFLINE_CONFIG" != "true" && die "WARNING: Using failsafe shell" >"$OUT_TTY"			
		
		;;	
	esac
}


wait_for_input() {

	# Neo has only two buttons: AUX and PWR.
	# Only AUX is easily readable from userspace, so are touchscreen taps
	if test "$KBD_INT" = "Neo1973 AUX button"
	then
		ints_old="`cat /proc/interrupts | grep "$KBD_INT" | awk '{print $2}'`"
		ts_ints_old="`cat /proc/interrupts |grep action| tail -1 | awk '{print $2}'`"
		
		echo "Please press [AUX] to set a menu number" <"$OUT_TTY" > "$OUT_TTY" 2>&1	
		echo -e "Press the touchscreen to start\n"			
		
		x="$last_selection_num" ; hold_events=0
		
		echo -n "Your Choice: [$x / $cnt]"
		
		while true 
		do

			ts_ints_now="`cat /proc/interrupts |grep action| tail -1 | awk '{print $2}'`"

			if test "$ts_ints_now" = "$ts_ints_old"
			then
				ints_now="`cat /proc/interrupts | grep "$KBD_INT" | awk '{print $2}'`"

				if test "$ints_now" -gt "$ints_old"
				then
					# Only react on every second interupt as both PRESS and RELEASE generate
					# one.
					if test -n "$tmp"
					then 
						#debug_echo "wait_for_input(): PRESS/RELEASE EVENT [$ints_now <-> $ints_old]"				
						
						MAX_ENTRIES="$cnt"
						
						test "$x" = "$cnt" && x=1 || let x=$x+1
						
						#\r : go to beginning of the current line
						#\033[K : completely clear the current line
						echo -en "\r\033[KYour Choice: [$x / $cnt]"
												
						tmp=""
					else
						tmp=blahh				
					fi
				fi
			
				ints_old="$ints_now"
			else				
#				debug_echo "wait_for_input(): TOUCHSCREEN EVENT [$ts_ints_now <-> $ts_ints_old]"
				ts_ints_old="$ts_ints_now"				
				
				junk="$x"
				break				
			fi		
		done
	else
		while true
		do		

			# Do _not_ change the next few lines!
			# 	
			# This is required to work around an annoying busybox bug.
			# Every key you press while this script runs will be
			# picked up by the next "read $junk".	
			# So the next read would pick up the "any" key the user pressed
			# above to launch the altboot menu.		


			# Bash throws an ugly error on kill
			if ! (readlink /bin/sh | grep -q bash)
			then
				# This filters an "<ENTER>" from the user as "any key"
				( while :; do read x< "$OUT_TTY" 2>&1; done; ) > /dev/null 2>&1 &
				sleep 1; kill $! >/dev/null 2>&1
			fi

			echo -n "Please choose one of the above [$last_selection_num]: " <"$OUT_TTY" > "$OUT_TTY" 2>&1
			stty echo <"$OUT_TTY" >"$OUT_TTY" 2>&1
			read junk< "$OUT_TTY" 2>&1

			# This filters other chars the user may have used

			junk="`echo "$junk" | sed "s/[a-zA-Z]//g"`"	

			if test "$junk" -lt "$cnt" -o "$junk" -eq "$cnt" 
			then			
				if test -z "$junk" 
				then
					junk="$last_selection_num"
					parse_module_flags "$junk"				
					break
				fi
				break
			fi
		done
	fi
}

# * * * * * * This is the main function * * * * * *

if ( echo "$VERSION" |  egrep -iq "(snapshot|-rc)" )
then
	if test "$ENABLE_DEBUGGING" = "auto" -o -z "$ENABLE_DEBUGGING"
	then
		ENABLE_DEBUG="yes"		
	fi
fi

test "$ENABLE_DEBUG_SHELL" = "yes" && debug_shell 4 >/dev/null 2>&1 &

if test "$ENABLE_DEBUGGING" = "yes"
then
	ENABLE_DEBUG="yes"
	debug_shell 4 >/dev/null 2>&1 &
fi

# Note: this is positively ugly. If someone knows a better way to detect whether
# we are already booted into a runlevel _without_ reading /var and / or using `runlevel`
# PLEASE let me know.
#
# The NSLU2 is an exception as it uses the way-ugly busybox "ps"
#

mount | grep -q "^proc" || mount -t proc proc /proc >/dev/null 2>&1
MACHINE="`cat /proc/cpuinfo | sed -n "/^Hardware/s/.*\:\ \(.*\)/\1/p"`"

# We were called by the user via /sbin/altboot
if test "`basename "$0"`" = "altboot"
then
	OFFLINE_CONFIG="true"
	. /etc/altboot.sbin
	
	exit 0
fi

case "$MACHINE" in
	*NSLU2)	if test -f /proc/cmdline -a "`ps |wc -l|tr -d " "`" -gt 23 -a "$1" != "-force" -a "$1" != "+force" 
		then			
	   		user_called=1
		fi			
		;;
		
	*)	if test -f /proc/cmdline -a "`ps ax|wc -l|tr -d " "`" -gt 30 -a "$1" != "-force" -a "$1"!= "+force" >/dev/null 2>&1	
	   	then
	   		user_called=1
	   	fi
	   	;;
esac

if test "$user_called" = "1"
then
	echo "altboot: Using real init [$REAL_INIT] [$*] [`ps |wc -l|tr -d " "`] *" >"$OUT_TTY"
	exec $REAL_INIT $*
#	exec $SH_SHELL </dev/tty0 >/dev/tty0 2>&1
	exit 0
else	
	# Boot original init if altboot is turned off
	if test "$ENABLE_ALTBOOT" != "yes" 
	then	
		echo "altboot: Using real init [$REAL_INIT] **" >"$OUT_TTY"
		exec $REAL_INIT $INIT_RUNLEVEL
		exit 0
	fi
	
	# Execute scripts in /etc/altboot.rc before doing anything else.
	# Required in special situations, like booting spitz
	RC_FILES=`ls /etc/altboot.rc | grep \.sh$`

	for file in $RC_FILES
	do
		 . /etc/altboot.rc/$file >"$OUT_TTY" 2>&1 || echo "/etc/altboot.rc/$file failed!"
	done
			
	# Make sure altboots master password is set
	set_password >"$OUT_TTY" 
	
	test "$ASK_PW_ON_BOOT" = "yes" && verify_master_pw >"$OUT_TTY" 

	# When started with -force, always print the menu
	echo "$*" | grep -q -- "-force" && TIMEOUT=0
	
	# This timeout works by reading /proc/interrupts to see if the keyboard interrupt
	# increases while the timer is running. A TIMEOUT of 0 will always launch altboot.	
	run_timer >"$OUT_TTY" 
		
	echo "" >"$OUT_TTY"
	
	# last_selection is the previously selected menu item by the user
	last_selection_num="`cat "$ALTBOOT_LAST_FILE" |awk '{print $1}'`" >/dev/null 2>&1
	last_selection_data="`cat "$ALTBOOT_LAST_FILE" | sed -n "s/[0-9]\{1,\}\ \(.*\)$/\1/p"`"				
	test -z "$last_selection_num" && last_selection_num="1"	
	
	# launch_altboot is set to "yes" by run_timer when the user presses the button during the timeout
	if test "$launch_altboot" != yes
	then		
		echo "Booting last selection: [$last_selection_num]" >"$OUT_TTY"
		
		# Set up the wanna-be array of available menu entries and their numbers
		show_menu /etc/altboot-menu >/dev/null 2>&1

		junk="$last_selection_num" ; AUTOSTART="true"
		launch_selection /etc/altboot-menu >"$OUT_TTY"
	fi

	# Anything after this point will never be reached if $launch_altboot != yes	
	
	# Show the altboot menu
	stty -echo <"$OUT_TTY" >"$OUT_TTY" 2>&1
	show_menu /etc/altboot-menu >"$OUT_TTY"

	# Ask the user which menu-item to use
	wait_for_input >"$OUT_TTY" 
	
	# This should _never_ happen.
	if test -z "$junk"
	then
		echo "WARNING: Trying failsafe mode" >"$OUT_TTY"
		mount -o remount,rw / >"$OUT_TTY" 2>&1
		echo "Dumping environment to /altboot.env"
		echo "$CURRENT_ENV" > /altboot.env
		mount -o remount,ro / >"$OUT_TTY" 2>&1
		junk=1
	fi

	launch_selection /etc/altboot-menu >"$OUT_TTY"	
	
	# Uhoh, something went terribly wrong if we reach this point!
	die "WARNING: Failsafe fall-through activated. Spawning emergency shell"	

fi