#! /bin/bash
# $Id: lilo.new 911 2006-11-27 23:59:03Z olh $
#
# a simple lilo to store the boot loader and the kernel images 
# in bash2 ... Think different [tm]
#
# olh@suse.de
# jplack@suse.de
#

# interprete a file like the following:


## Modified by YaST2. Last modification on Sun Mar  7 16:27:46 2004
##
##
## default = linux
## timeout = 100
## boot = /boot/suse_linux_bootfile
## boot = B
##
## image = /boot/vmlinux
##     ###Don't change this comment - YaST2 identifier: Original name: linux###
##     label = Linux
##     root = /dev/iseries/vda3
##     append = "hwinfo=-cdrom 3 desktop"
##

## requires    fdisk   parted  stat tr


# Language kills all parsing effort
unset LANG
unset LC_CTYPE

CONFIG_LILO_CONF=/etc/lilo.conf
SHOW_OF_PATH="/bin/show_of_path.sh --quiet"
DEFAULT_BOOTFOLDER=suseboot
TEMP="${TMPDIR:-/tmp}/ppc_lilo"
MACHINE=
OPTION_USE_OS_CHOOSER=yes
_sysfs_mounted=
_proc_mounted=

shopt -s extglob


FDISK=/sbin/fdisk
PARTED="/usr/sbin/parted -s"
PVERSION=$($PARTED --version)		# e.g 'GNU Parted 1.6.22'
PVERSION=${PVERSION#GNU Parted}
read a b c d e f  <<< "${PVERSION//./ }"
PVERSION=$(( a*1000000 + b*1000 + c ))	# e.g. 1006022
STAT_CMD=/usr/bin/stat


# trap EXIT function
function clean_environment () {
    cd /
    [[ $(</proc/mounts) == *$TEMP/boot\ * ]] && umount $TEMP/boot
    rm -rf $TEMP
    [ $_sysfs_mounted ] && umount /sys
    [ $_proc_mounted ] && umount /proc
} #end function clean_environment


function Usage() {
       cat  <<EOHELP
lilo for PowerPC 10.1.21
configures the Linux Loader on a few different PowerPC board types,
based on the configuration file /etc/lilo.conf.
This includes IBM RS/6000 and pSeries models, IBM legacy iSeries,
Apple PowerMacs (OldWorld and NewWorld) and older PReP based boards.

Known options are:
    --quiet         sets an internal variable ;-)
    --debug | -d    sets another internal variable ;-)
    --get-arch      returns the board type lilo is running on
                    known values are:
		    iseries chrp pmac_new pmac_old pegasos
    --version | -v  displays the version number
    --help          displays this help text
EOHELP
}


function error() {
    _severity="ERROR"
    _error "$@"
    exit 1
}

function warning() {
    _severity="Warning"
    _error "$@"
}


function _error() {
    # helper function to print _all_ error messages in a unified form
    # argument list consists of a list of msg numbers and custom strings
    until  [ "$#" = 0 ] ; do
	case "$1" in
	    1)
	        echo '************************************************************'
	       	echo '* You must create a PPC PReP Boot partition (type 0x41) or *'
	       	echo '* a FAT partition for the CHRP bootloader to be installed. *'
	       	echo '************************************************************'
		;;
	    2)
	       	echo '************************************************************'
	       	echo '* There is more than one PPC PReP Boot (type 0x41) or FAT  *'
	       	echo '* on this system. Please specify the boot partition NUMBER *'
	       	printf '* in boot= %-30s                  *\n' $err_var1
	       	echo '************************************************************'
		;;
	    3)
	        echo '************************************************************'
		echo '* You have to create a PPC PReP Boot partition (type 0x41) *'
		echo '* for the kernel binary to be installed.                   *'
		echo '************************************************************'
		;;
	    4)
       	    	echo '************************************************************'
	    	echo '* You have to create a PPC PReP Boot partition (type 0x41) *'
	    	echo '* for the CHRP bootloader to be installed.                 *'
	    	echo '************************************************************'
		;;
	    5)
	       	echo '************************************************************'
	       	echo '* There is more than one PPC PReP Boot partition (type     *'
	       	echo '* 0x41) on this system. Please specify the boot partition  *'
	        printf '* NUMBER in boot = %-30s          *\n' $err_var1
	       	echo '************************************************************'
		;;
	    6)
	        echo "${_severity}: in config file, no label in section image/other = $err_var1"
		;;
	    7)
	        echo "${_severity}: in config file, $option option must not be in an image/other section!"
		;;
	    8)
		echo "${_severity}: in config file, $option option has to be in an image/other section!"
		;;
	    9)
	        echo "${_severity}: in config file, boot = $err_var1 is not 41 PReP"
		;;
	    10)
	        echo "${_severity}: in config file, guessing of boot partition failed"
		;;
	    11)
		echo "${_severity}: parted complains:"
		cat $TEMP/parted.log
		;;
	    12)
	       	echo '************************************************************'
		echo '*  We cannot guarantee a proper reboot with your current   *'
		echo '* non trivial boot configuration which relies on a working *'
		echo '*               FAT firmware driver from IBM               *'
		echo '*                                                          *'
	  	echo '* Please consult your local IBM representative so that he  *'
		echo '*     can take the appropriate steps to help you out.      *'
	       	echo '************************************************************'
		;;
	    42)
	        if [ "$OPTION_FORCE" ]; then return; fi
		echo
		echo "If you really want to try this on your own risk, you can add"
		echo "a line saying      \"force\"      into the global section of"
		echo "your /etc/lilo.conf and rerun /sbin/lilo"
		;;
	
	    *)  # plain custom string
	        echo "${_severity}: $1"
		;;
	esac >&2
	shift
    done   
}


function get_of_path() {
    local errnum=$?

    $SHOW_OF_PATH $1
    errnum=$?

    #if (( errnum != 0 )); then
    #	error "show_of_path.sh returned error $errnum while analysing path $1"
    #fi
}


function is_on_primary() {
    local of_path="$1"

    # check for invalid of path
    [ "$of_path" ] || return 1;

    # check whether file is on a primary partition [1234]
    if [[ "${of_path%,/*}" != *:[1234] ]]; then return 1; fi

    return 0
}

# convert a number of 512 byte sectors in a format that parted accepts and the
# other way `round. Behaviour changed in subminor version. Hurray :-(
if (( PVERSION > 1006022 )); then

    function sect2parted() {
	local s=$1
	echo ${s}s
    }
    function parted2sect() {
	local s=$1
	if [ ${s:0-1} == "s" ]; then
	    echo  ${s/%s}
	else
	    echo 0
	fi
    }
    function parted_print(){
	local device=$1
	$PARTED "$device" unit s print | sed '/^[[:blank:]]\+[0-9]/s@^[[:blank:]]\+@@'
    }

else

    function sect2parted() {
	local s=$1
	s=$(( (s + 1) * 1000 / 2 / 1024 ))
	while (( ${#s} < 4 )); do s="0$s"; done
	echo ${s%???}.${s:$[${#s}-3]}
    }
    function parted2sect() {
	local s=$1
	s=${s/.}
	s=${s##*(0)}
	echo $(( s * 1024 * 2 / 1000 ))
    }
    function parted_print() {
	local device=$1
	$PARTED "$device" print
    }

fi

function parted_call() {
    # call parted binary
    $PARTED "$@"
    local ret=$?

    # wait for pending triggered udev events.
    # until we know how to do better, just wait for _all_ events to finish
    local loop=300	# wait for a maximum of 30sec
    while [ -d /dev/.udev/queue ] && (( loop-- > 0 )); do
	sleep 0.1
    done

    return $ret
}

function real_device () {
    # print the real device i.e. map partitions to disks
    local -i dev=$1 real=0
    local -i major minor
    local range
    local IFS=:

    while read range; do
	read major minor < ${range/range/dev}
	(( real = major * 256 + minor ))
	range=$(<$range)
	if (( dev >= real && dev < real + range )); then
	    echo $real
	    return;
	fi
    done < <( find /sys/block -name range )

    echo 0000
}

function device_of_file () {
    # print device number in decimal given a file path
    local f="$1";
    
    if [ ! -x $STAT_CMD -o -z "$f" ]; then
	# be nice if /usr not mounted
	echo 0000
	return;
    fi
    
    $STAT_CMD --format="%d" $f
}


function device_nr () {
    # print device number in decimal given a path to a device node
    local d="$1"
    
    # /dev/root is an alias to the root file system
    if [ "$d" == "/dev/root" ]; then
	device_of_file /
	return;
    fi

    if [ ! -x $STAT_CMD ]; then
	# be nice if /usr not mounted
	echo 0000
	return;
    fi

    # test whether its a block device ?
    if [ ! -b "$d" ]; then
	# this should never happen
	echo >&2 "Path '$d' does not point to a block device node!"
	echo 0000
	return;
    fi

    echo $[ $($STAT_CMD --dereference --format "0x%t*256 + 0x%T" $d) ]
}


function fsize() { # get file size in blocks
    local s f r
    if [ -z "$1" ]; then
	echo 0
    else
	read s f r < <(ls -Ls --block-size=512 $1)
	echo $s
    fi
}

function find_interrupt_controller_property() {
	# to be called with "/proc/device-tree" as entry directory
	for f in "$1"/*
	do
		if test -L "$f"
		then
			continue
		fi
		if test -d "$f"
		then
			find_interrupt_controller_property "$f"
			if test "$?" = "1"
			then
				return 1
			fi
		fi
		if test -f "$f"
		then
			if test "${f##*/}" = "interrupt-controller"
			then
				return 1
			fi
		fi
	done
	return 0
}

function check_arch () {
	# check for the current ppc subarch
	# this function set the global MACHINE to either
	# * iseries
	# * pmac_new
	# * pmac_old
	# * chrp
	# * pegasos
	# * prep
	# or nothing at all if the boardtype is not recognized
	# 
	local prop
	local board_type

	#
	if test -d /proc/iSeries
	then
		board_type="iseries"
	fi
	#
	if test -z "$board_type"
	then
		if test -f /proc/device-tree/compatible
		then
			prop="`tr '\0' '\n' < /proc/device-tree/compatible`"
		else
			read prop < /proc/device-tree/model
		fi
		: prop $prop
		case "$prop" in
			*MacRISC*)
				board_type=pmac
				;;
			*Power\ Macintosh*)
				board_type=pmac
				;;
			IBM,*)
				board_type=chrp
				;;
			Momentum,Maple-D)
				board_type=chrp
				;;
			Momentum,Maple-L)
				board_type=chrp
				;;
			Momentum,Maple)
				board_type=chrp
				;;
			Pegasos2)
				board_type=pegasos
				;;
		esac
		if test "$board_type" = "pmac"
		then
			# PowerMacs with an "interrupt-controller" property are all NewWorld
			if find_interrupt_controller_property /proc/device-tree
			then
				board_type=pmac_old
			else
				board_type=pmac_new
			fi
		fi
	fi

	if [ -z "$board_type" ]; then
	    case $(</proc/cpuinfo) in
		*PReP*)
		    board_type=prep
		    ;;
		*CHRP*)
		    board_type=chrp
		    ;;
	    esac
	fi
	if [ -z "$board_type" ]; then
	    case $(</proc/device-tree/device_type) in
		chrp)
		    board_type=chrp
		    ;;
	    esac
	fi
		
	MACHINE="$board_type"

} #end function check_arch


#
#   main script
#

#  parse options
#
while [ "$1" ]; do case "$1" in
    --quiet|-q)
       quietmode=1
       ;;
    --debug|-d)
       debug=1
       ;;
    --get-arch)
       get_arch=1
       ;;
    --version|-v)
       echo "lilo for PowerPC 10.1.21"
       exit 0
       ;;
    --help)
       Usage
       exit 0
       ;;

    *)
        echo 1>&2 "lilo: Option \"$1\" not supported"
	Usage;
        exit 1
	;;
esac; shift; done

function firmware_status () {
    source /lib/lilo/lilo-chrp.lib
    firmware_status;
}


function running_on_iseries () {
    source /lib/lilo/lilo-iseries.lib
    running_on_iseries;
}


function running_on_chrp () {
    source /lib/lilo/lilo-chrp.lib
    running_on_chrp;
}


function running_on_prep () {
    source /lib/lilo/lilo-chrp.lib
    running_on_prep;
}

function running_on_pegasos () {
    source /lib/lilo/lilo-chrp.lib
    running_on_pegasos;
}

function running_on_pmac_old () {
    source /lib/lilo/lilo-pmac.lib
    running_on_pmac_old;
}


function running_on_pmac_new () {
    source /lib/lilo/lilo-pmac.lib
    running_on_pmac_new;
}


function parse_config_file () {
    # parse the lilo.conf and place it in CONFIG_IMAGE_FILE[]
    # other vars:
    # OPTION_BOOT is an array to contains the bootloader partition and/or targets
    # OPTION_BOOT_COUNT is a counter to the array above
    # OPTION_OTHER contains the MacOS partition
    # OPTION_BOOTFOLDER contains the MacOS folder with the bootstuff
    # OPTION_ACTIVATE is a flag whether or not the boot partition must be set active in OF
    # OPTION_USE_OS_CHOOSER is a flag whether or not the Forth script used as bootfile will open the screen
    # OPTION_TIMEOUT contains the timeout variable in seconds
    # OPTION_MACOSTIMEOUT contains the timeout between linux/macos in seconds
    # OPTION_DEFAULT contains the default label
    # OPTION_ROOT contains the global or local root= device
    # OPTION_APPEND contains the global or local append= strings
    # OPTION_INITRD containes the global or local initrd filename
    # OPTION_IMAGE_COPY contains a flag to force copy to the boot partition
    # CONFIG_PARSE_HASIMAGE is a flag if we have a image section.
    # CONFIG_IMAGE_FILE array contains the kernel image for a section
    # CONFIG_IMAGE_OTHER array contains the device of MacOS
    # CONFIG_IMAGE_COPY array contains copy flag for images on macs
    # CONFIG_IMAGE_OPTIONAL array contains flag about image/initrd availibility
    # CONFIG_IMAGE_COUNT is a simple counter of image sections
    declare option
    local separator value

    if [ ! -f $CONFIG_LILO_CONF ]; then
	error "Config file $CONFIG_LILO_CONF not found"
    fi

    unset CONFIG_PARSE_HASIMAGE
    CONFIG_IMAGE_COUNT=0
    OPTION_BOOT_COUNT=0
    while read; do
	# strip comments, heading and trailing whitespace and empty lines
	REPLY=${REPLY%%#*}
	REPLY=${REPLY%%+([ 	])}
	REPLY=${REPLY##+([ 	])}
	if [ -z "$REPLY" ]; then continue; fi
	REPLY=${REPLY/=/ = }
	REPLY=${REPLY/\"\"/}	# replace quoted empty string by "itself"
 
	read option separator value <<< "$REPLY"
	#	echo option "$option"
	#	echo separator "$separator"
	#	echo value $value

	if [ -n "$separator" ] && [ "$separator" != "=" ]; then
	    echo "Illegal separator '$separator', line ignored"
	    continue
	fi

	case "$option" in
	    boot)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		if [ -L "$value" ]; then
		    OPTION_BOOT[$OPTION_BOOT_COUNT]=$(readlink -f $value)
		else
		    OPTION_BOOT[$OPTION_BOOT_COUNT]=$value
		fi
		let OPTION_BOOT_COUNT++
		;;
	    clone)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		# this is valid for chrp only ...
		if [ -L "$value" ]; then
		    OPTION_CLONE=$(readlink -f $value)
		else
		    OPTION_CLONE=$value
		fi
		;;
	    activate)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_ACTIVATE="yes"
		;;
	    no_os_chooser)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_USE_OS_CHOOSER="no"
		;;
	    force_fat)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_FORCEFAT="yes"
		;;
	    force)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_FORCE="yes"
		;;
	    bootfolder)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_BOOTFOLDER=":${value}"
		;;
  	    timeout)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_TIMEOUT=$value
		;;
  	    macos_timeout)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_MACOSTIMEOUT=$value
		;;
	    default)
		[ "$CONFIG_PARSE_HASIMAGE" ] && error 7
		OPTION_DEFAULT=$value
		;;
	    image)
		# check if previous image section has a label
		if [ "$CONFIG_PARSE_HASIMAGE" ] ; then
		    err_var1="${CONFIG_IMAGE_FILE[$CONFIG_IMAGE_COUNT]:-${CONFIG_IMAGE_OTHER[$CONFIG_IMAGE_COUNT]}}"
		    if [ -z "$err_var1" ]; then	error 6; fi
		fi
		CONFIG_PARSE_HASIMAGE=true
		let CONFIG_IMAGE_COUNT++
		CONFIG_IMAGE_FILE[$CONFIG_IMAGE_COUNT]=$value
		CONFIG_IMAGE_OPTIONAL[$CONFIG_IMAGE_COUNT]="no"
		;;
	    other)
		# check if previous image section has a label
		if [ "$CONFIG_PARSE_HASIMAGE" ] ; then
		    err_var1="${CONFIG_IMAGE_FILE[$CONFIG_IMAGE_COUNT]:-${CONFIG_IMAGE_OTHER[$CONFIG_IMAGE_COUNT]}}"
		    if [ -z "$err_var1" ]; then	error 6; fi
		fi
		CONFIG_PARSE_HASIMAGE=true
		CONFIG_PARSE_HASOTHER=true
		let CONFIG_IMAGE_COUNT++
		CONFIG_IMAGE_OTHER[$CONFIG_IMAGE_COUNT]=$value
		OPTION_OTHER=$value
		;;
	    root)
		# FIXME: fix some common typos like missing quotes of spaces
		# like that? "'${a//*( )=*( )/=}'"
		if [ -z "$CONFIG_PARSE_HASIMAGE" ] ; then
		    OPTION_ROOT=$value
		else
		    CONFIG_IMAGE_ROOT[$CONFIG_IMAGE_COUNT]=$value
		fi
		;;
	    copy)
		[ "$CONFIG_PARSE_HASIMAGE" ] || error 8
		CONFIG_IMAGE_COPY[$CONFIG_IMAGE_COUNT]="true"
		;;
	    optional)
		[ "$CONFIG_PARSE_HASIMAGE" ] || error 8
		CONFIG_IMAGE_OPTIONAL[$CONFIG_IMAGE_COUNT]="yes"
		;;
	    label)
		[ "$CONFIG_PARSE_HASIMAGE" ] || error 8
		CONFIG_IMAGE_LABEL[$CONFIG_IMAGE_COUNT]=$value
		;;
	    append)
		# FIXME: fix some common typos like missing quotes of spaces
		# like that? "'${a//*( )=*( )/=}'"
		if [ -z "$CONFIG_PARSE_HASIMAGE" ] ; then
		    # use eval to strip ""
		    eval OPTION_APPEND=$value
		else
		    # use eval to strip ""
		    eval CONFIG_IMAGE_APPEND[$CONFIG_IMAGE_COUNT]=$value
		fi
		;;
	    sysmap)
		echo "sysmap= is not required anymore, remove it from your lilo.conf file"
		;;
	    initrd)
		if [ -z "$CONFIG_PARSE_HASIMAGE" ] ; then
		    OPTION_INITRD=$value
		else
		    CONFIG_IMAGE_INITRD[$CONFIG_IMAGE_COUNT]=$value
		fi
		;;		
	    *)
	        error "!!!!!!!!!! unkown option $option !!!!!!!!!!!!!"
		;;
	esac
    done < $CONFIG_LILO_CONF
    
} #end function parse_config_file


function check_config_file () {
    if [ -z $OPTION_BOOT ]; then
	#   work around a YaST bug, which comes from a certain kind of
	#   intel-architecture blindness, some architectures can guess boot
	#   drive :-)
	if [[ "$MACHINE" != @(chrp|prep|pegasos) ]]; then
	    error "boot=<partition> is not specified!"
	fi
    else
	if [ "$MACHINE" != "iseries" -a "$MACHINE"  != "pegasos" ]; then
	    if [ ! -b $OPTION_BOOT ]; then
		error "boot = $OPTION_BOOT is not a valid block device"
	    fi
	    if [ $OPTION_BOOT_COUNT -ne 1 ]; then
		error "only one boot= line allowed here!"
	    fi
	fi
    fi
    if [ -z "$CONFIG_PARSE_HASIMAGE" ] ; then
	error "no image section is specified"
    fi
    
    for (( i=1; i<=CONFIG_IMAGE_COUNT; i++ )); do
	if [ ! -f ${CONFIG_IMAGE_FILE[$i]} ] ; then
	    if [ ${CONFIG_IMAGE_OPTIONAL[$i]} = "no" -o "${CONFIG_IMAGE_LABEL[$i]}" = "$OPTION_DEFAULT" ] ; then
		error   "${CONFIG_IMAGE_LABEL[$i]}: image = ${CONFIG_IMAGE_FILE[$i]} ist not a regular file"
	    else
		warning "${CONFIG_IMAGE_LABEL[$i]}: image = ${CONFIG_IMAGE_FILE[$i]} ist not a regular file"
		CONFIG_IMAGE_OPTIONAL[$i]="skip"
	    fi
	fi
	if [ ! -f ${CONFIG_IMAGE_INITRD[$i]} ] ; then
	    if [ ${CONFIG_IMAGE_OPTIONAL[$i]} = "no" -o "${CONFIG_IMAGE_LABEL[$i]}" = "$OPTION_DEFAULT" ] ; then
		error   "${CONFIG_IMAGE_LABEL[$i]}: initrd = ${CONFIG_IMAGE_INITRD[$i]} ist not a regular file"
	    else
		warning "${CONFIG_IMAGE_LABEL[$i]}: initrd = ${CONFIG_IMAGE_INITRD[$i]} ist not a regular file"
		CONFIG_IMAGE_OPTIONAL[$i]="skip"
	    fi
	fi
    done
    
    #
    #  loop for multiple OPTION_BOOT entries
    #
    for (( i=0; i<OPTION_BOOT_COUNT; i++ )); do
	OPTION_DEVICE[$i]="${OPTION_BOOT[$i]%%+([0-9])}"	 # split boot option to
	OPTION_PARTITION[$i]="${OPTION_BOOT[$i]##*[^0-9]}"	 # dev & part for parted
    done
} #end function check_config_file


# check for requirements:
#   /proc
#   /sys

# assert that /proc is mounted, else try to mount, on fail complain
if test -d /proc/1; then
    :
elif mount -t proc proc /proc; then
    _proc_mounted=1
else
    error "proc not mounted and attempt to mount /proc failed"
fi


# assert that /sys is mounted, else try to mount, on fail complain
if test -d /sys/block; then
    :
elif mount -t sysfs sysfs /sys; then
    _sysfs_mounted=1
else
    error "sysfs not mounted on /sys and attempt to mount failed" "may be no kernel 2.6.x?"
fi


#
# prepare environment:
#
rm -rf $TEMP
mkdir -p $TEMP
trap "clean_environment" EXIT INT

if test -f /proc/device-tree/openprom/model ; then
	if grep -q IBM,SLOF /proc/device-tree/openprom/model ; then
		error "SLOF has no working CLAIM implementation" \
		"booting CD1/suseboot/inst64 via the network is currently the only way to boot SuSE Linux" \
		"" \
		" THIS IS A FIRMWARE BUG! " \
		"" \
		"Can not proceed with bootloader installation."
	fi
fi

#
# here we go
#

check_arch
if [ "$get_arch" ]; then
    echo "$MACHINE"
    exit 0
fi
parse_config_file
check_config_file

case "$MACHINE" in
    pmac_new)  running_on_pmac_new ;;
    pmac_old)  running_on_pmac_old ;;
    chrp)      running_on_chrp     ;;
    prep)      running_on_prep     ;;
    iseries)   running_on_iseries  ;;
    pegasos)   running_on_pegasos  ;;
esac

#
#
# Local variables:
#     mode: sh
#     mode: font-lock
#     mode: auto-fill
#     sh-indent: 4
#     sh-multiline-offset: 2
#     sh-if-re: "\\s *\\b\\(if\\)\\b[^=]"
#     fill-column: 78
# End:
#
