#!/bin/bash
#
# Shell script to start Mac-on-Linux
#
# Usage: startmol [OPTION] [vmlinux]... [System.map]...
#

MOL_OPTS="x"

# --elf is handled specially
MOL_LONGOPTS="session vt ram test"


###########################################################################
# Extract command line options (some for MOL, some for us)
###########################################################################

LOPTS="" ; OPTS="" ; ARGS=""
while [ "$*" != "" ] ; do
    case $1 in 
	--*) LOPTS="$1 $LOPTS";;
	-*) OPTS="$1 $OPTS" ;;
	*) ARGS="$1 $ARGS" ;;
    esac
    shift 1
done

MOLARGS="" ; FLAGS=""
while getopts ":$MOL_OPTS" x $OPTS ; do
    if [ "$x" == "?" ] ; then
	FLAGS="$OPTARG $FLAGS"
    else
	MOLARGS="-$x $MOLARGS"
    fi
done

for x in $LOPTS ; do
    MATCH=0
    for y in $MOL_LONGOPTS ; do
	{ [ "$x" == "--$y" ] || [ "${x##--$y=*}" == "" ]; } && MATCH=1;
	if [ "$MATCH" == 1 ] ; then
	    MOLARGS="$MOLARGS $x"
	    break;
	fi
    done
    [ "$MATCH" == 0 ] && FLAGS="$FLAGS $x"
done


###########################################################################
# Parse our options
###########################################################################

unset _HELP _VERSION _LOADONLY _MATCH _LIST _ELFFILE _DEBUG
for x in $FLAGS ; do 
    case $x in 
	h|--help)		_HELP=1 ;;
	V|--version)		_VERSION=1 ;;
	l|--loadonly|--load)	_LOADONLY=1 ;;
	--list)			_LIST=1 ;;
	e|--exact)		_MATCH=1 ;;
	a)			_MATCH=0 ;;
	d|--debug)		_DEBUG=1 ;;
	--elf*)			_ELFFILE=${x/--elf=/} ;;
	*)
	    echo -e "Unknown option '$x'\n" ; _HELP=1;  break; ;;
    esac
done

[ "$_HELP" == 1 ] && { 
    echo -ne "Usage: startmol [OPTIONS] [kernelimage]... [System.map]..."\
	    "\nStarts Mac-on-Linux (MOL)\n\n"\
	    " -a                  allow a small kernel module version mismatch\n"\
	    " -d, --debug         run the debugger\n"\
	    " -x,                 wait for the debugger (moldeb) to be started\n"\
	    " -e, --exact         require exact kernel version match\n"\
	    " -h, --help          display this help text and exit\n"\
	    " -l  --loadonly      do not start MOL, just load the kernel modules\n"\
	    " -V, --version       output version information and exit\n"\
            "     --elf=image     load and run statically linked ELF-image inside MOL\n"\
	    "     --list          list available MOL kernel modules\n"\
	    "     --ram=SIZE      RAM size (in megabytes)\n"\
            "     --session=num   run as session num\n"\
            "     --test          run self-test and exit\n"\
            "     --vt=num        use VT<num> for the full-screen video\n"\
	    "\nIf specified, kernelimage must be an image of the running kernel"\
	    "\n(e.g. /usr/src/linux/vmlinux). The System.map file should also correspond"\
	    "\nto the running kernel\n"\
	    "\nFor more information, please visit <http://www.maconlinux.org>.\n"
    exit 1;
}

function get_abs_path()
{
    echo $1 | grep '^/' > /dev/null&& { echo $1; return 0; } 
    echo `pwd`/$1
    return 0
}

# This must be done before we change working directory...
[ "$_ELFFILE" != "" ] && MOLARGS="$MOLARGS --elf=`get_abs_path $_ELFFILE`"
[ "$_DEBUG" == "1" ] && MOLARGS="$MOLARGS -d"

###########################################################################
# extract System.map or kernel binary arguments from the cmdline
###########################################################################

SYMFILES=( $ARGS )

[ "${SYMFILES[*]}" == "" ] && {
    SYMFILES=( /var/tmp/System.map-`uname -r` \
	    /boot/System.map-`uname -r` \
	    /boot/System.map /usr/src/linux/System.map )
}


###########################################################################
# Find out where our binaries and library files are
###########################################################################
#
# Normally, the mol library path is hardcoded or obtained from 
# /etc/molrc. However, when we are started by root we should be 
# aware of the case when MOL is started directly from the source
# tree.

unset MOL_LIB_DIR

if [ $UID -eq 0 ] ; then
    if [ -d "mollib/pci_roms" -a -f "Makefile" ] ; then
	MOLRCGET=`pwd`/emulation/molrcget
	MOL_LIB_DIR=`$MOLRCGET -p` || exit 1
	MOL_BINLIB_DIR="`pwd`/mollib" || exit 1
	MOL_DBG="`pwd`/debugger/moldeb"
	MOL_MOD_DIR="$MOL_BINLIB_DIR/modules"
	echo "Using binaries in $MOL_BINLIB_DIR and support files in $MOL_LIB_DIR."
    fi
fi
if [ "$MOL_LIB_DIR" == "" ] ; then
    MOLRCGET="molrcget"
    MOL_LIB_DIR=`$MOLRCGET -p` || exit 1
    MOL_BINLIB_DIR=`$MOLRCGET -P` || exit 1
    MOL_DBG="moldeb"
    MOL_MOD_DIR="$MOL_BINLIB_DIR/modules/`$MOLRCGET -V`"
fi

VERS=`cat /proc/version | awk -- '{ print $3 }' | sed s/[a-zA-Z].*// | sed s/\-.*//`
VERS_MAJOR=`echo $VERS | awk -F . -- '{ print $2 }'`
VERS_MINOR=`echo $VERS | awk -F . -- '{ print $3 }'`

# MOL_BINLIB_DIR is used by mol_uname
export MOL_LIB_DIR MOL_BINLIB_DIR
ORGDIR=`pwd`
cd $MOL_BINLIB_DIR

/sbin/lsmod | grep 'mol\>' > /dev/null && MOL_MOD_LOADED=1
/sbin/lsmod | grep 'sheep_net\>' > /dev/null && SHEEP_MOD_LOADED=1


###########################################################################
# Handle some of the switches
###########################################################################

function getres()
{
    $MOLRCGET $* -- $MOLARGS
}
function _mol_uname()
{
    $MOL_BINLIB_DIR/bin/mol_uname $* -- $MOL_MOD_DIR
}

[ "$_VERSION" ] && {
    echo "Mac-on-Linux `getres -V`"
    echo "Copyright (C) 1997-2001 Samuel Rydh <samuel@ibrium.se>"
    exit 1;
}

[ "$_LIST" == "1" ] && {
    echo "---------------------------------"
    echo "Installed modules:"
    echo "---------------------------------"
    _mol_uname -l
    echo "---------------------------------"
    echo "Running kernel:" `_mol_uname -p`
    echo "---------------------------------"
    echo "  -noav = No AltiVec"
    echo "  -smp  = Multiprocessor Kernel"
    echo "---------------------------------"
    exit 1
}


###########################################################################
# Make sure we have a working molrc file
###########################################################################

getres -t || exit $?

###########################################################################
# get_mod_name - lookup the module binary
###########################################################################

function get_mod_name ()
{
    _MOD_DIR=`_mol_uname -e`
    [ $? != 0 ] && {
	[ "$_MATCH" == "" ] && {
	    _MATCH=1
	    getres -b allow_kver_mismatch && _MATCH=0
	}

	[ "$_MATCH" == 1 ] && {
	    echo "===================================================================="
	    echo "  No mol-`getres -V` kernel modules corresponding to the running"
	    echo "  `_mol_uname -p` kernel were found ('startmol --list' displays"
	    echo "  installed version). Recompile the mol kernel modules (recommended)"
	    echo "  or try starting MOL using the '-a' switch. The '-a' flag can be"
	    echo "  made default by adding 'allow_kver_mismatch: yes' to /etc/molrc."
	    echo "===================================================================="
	    return 2
	} 1>&2

	_MOD_DIR=`_mol_uname -f`
	[ $? != 0 ] && {
	    echo "===================================================================="
	    echo "  No kernel module suitable for this kernel was found. The MOL"
	    echo "  kernel modules must be recompiled (refer to www.maconlinux.org"
	    echo "  for instructions). The command 'startmol --list' can be used"
	    echo "  to display installed kernel modules."
	    echo "===================================================================="
	    return 1;
	} 1>&2
    }
    _MOD_DIR="$MOL_MOD_DIR/$_MOD_DIR"

#    echo "Using kernel modules in:" $_MOD_DIR 1>&2 
    [ -f $_MOD_DIR/$1.o ] && {
	echo $_MOD_DIR/$1.o ; 
	return 0;
    }

    echo "The kernel module '$_MOD_DIR/$1.o' appears to be missing." 1>&2
    return 1
}


###########################################################################
# Extract symbols
###########################################################################

TMPFILE=/tmp/System.map-tmp

# RSYMS - required symbols
RSYMS=( handle_mm_fault next_mmu_context flush_hash_page )

# sym_load_module_helper file required_flag symbols ...
function sym_load_module_helper ()
{
    i=0; ERR=0; XERR=0; FNAME="$1";
    shift 2

    for Y in $* ; do
	    X=`grep ' '$Y'$' $FNAME` || ERR=1
	    X=`echo $X | awk -- '{ print $1 }'`

	    [ $ERR -eq 1 ] && {
		    echo "ERROR: The kernel symbol '$Y' could not be found"
		    XERR=1; ERR=0
	    }
	    #echo $Y" = 0x"$X
	    SYM_STRING=( ${SYM_STRING[*]} msym_$Y=0x$X )
    done
    return $XERR
}

function sym_load_module ()
{
    SYM_STRING=""
    sym_load_module_helper $1 1 ${RSYMS[*]}
    # sym_load_module_helper $1 0 ${OSYMS[*]}
    G_MOD=`get_mod_name molsymglue` || exit 1
    echo -e "Loading MOL symglue kernel module\n   $G_MOD"
    /sbin/insmod -f $G_MOD ${SYM_STRING[*]} > /dev/null
}

function symbol_extract ()
{
    for x in ${SYMFILES[*]} ; do

	# expand relative paths
	y=${x##[!/]*}
	[ "$y" == "" ] && { y=$ORGDIR/$x; }

	[ ! -f $y ] && {
	    echo "The file '$y' is missing"
	    continue
	}
	echo "Examining '$y'"

	# detect and convert vmlinux files by checking the file size
	S=`wc -c $y | sed "s,^ *,," | cut -d" " -f 1`
	[ $S -ge 700000 ] && {
	    rm -f $TMPFILE
	    nm $y | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aU] \)|\(\.\.ng$$\)\|\(LASH[RL]DI\)'\
			| sort > $TMPFILE
	    y=$TMPFILE
	}

	$MOL_BINLIB_DIR/bin/symchecker.pl $y
	[ $? != 0 ] && continue

	CACHE_FILE="/var/tmp/System.map-`uname -r`"
	[ $CACHE_FILE != $y ] && {
	    echo "Caching kernel symbol file as $CACHE_FILE"
	    rm -f $CACHE_FILE
	    cp $y $CACHE_FILE
	}
	sym_load_module $y && SUCCESS=1
	rm -f $TMPFILE

	[ "$SUCCESS" == "1" ] && return 0;
    done
    return 1
}


###########################################################################
# load the MOL kernel module (if necessary)
###########################################################################

function load_mol_module ()
{
    MOL_MOD=`get_mod_name mol` || exit $?

    [ $UID != 0 ] && echo "Please run 'startmol' as root" && exit 1

    LOADGLUE=0
    /sbin/lsmod | grep 'molsymglue' > /dev/null
    [ $? == 0 ] || {
        # If the following symbols are defined, 'molsymglue' does
	# not need to be loaded.
	#
	# Recent 2.4 kernels:
	#	handle_mm_fault, next_mmu_context, flush_hash_page, 
	#
	# Old 2.4 kernels:
	#	handle_mm_fault, next_mmu_context, mol_interface
	#
	# Otherwise, we must load molsymglue, defining
	#	msym_handle_mm_fault, msym_next_mmu_context, msym_flush_hash_page

	SYMS_NEW=( handle_mm_fault next_mmu_context flush_hash_page )
	SYMS_OLD=( handle_mm_fault next_mmu_context mol_interface )

	for x in ${SYMS_NEW[*]} ; do
	    grep $x /proc/ksyms > /dev/null || LOADGLUE=1
	done
	[ $LOADGLUE != 0 ] && {
	    LOADGLUE=0
	    for x in ${SYMS_OLD[*]} ; do
		grep $x /proc/ksyms > /dev/null || LOADGLUE=1
	    done
	}
    }

    # As a last resort, extract the symbols by hand (molsymglue module)
    [ $LOADGLUE != 0 ] && {
	echo "Certain kernel symbols are missing. Trying to find a valid System.map file"

	symbol_extract
	[ $? != 0 ] && {
	    echo 
	    echo "========================================================================="
	    echo "  Unable to find the kernel symbol file. Start MOL by giving the"
	    echo "  command:"
	    echo 
	    echo "        startmol vmlinux"
	    echo 
	    echo "  where vmlinux is the image of the CURRENTLY running kernel. "
	    echo "  It is also possible to start MOL using the command"
	    echo 
	    echo "        startmol System.map"
	    echo 
	    echo "  where System.map is the symbol file of the running kernel."
	    echo "  For more information, please visit <http://www.maconlinux.org>"
	    echo "========================================================================="
	    echo 
	    exit 1
	}
    }

    echo -e "Loading Mac-on-Linux kernel module:\n   $MOL_MOD"
    /sbin/insmod -f $MOL_MOD
    [ $? != 0 ] && {
	echo "===================================================================="
	echo "  Failed to load the module - try recompiling the MOL kernel"
	echo "  module. Instructions (and information about common problems)"
	echo "  are available at <http://www.maconlinux.org>."
	echo "===================================================================="
	echo 
	exit 1
    }
    return 0
}

[ "$MOL_MOD_LOADED" != 1 ] && load_mol_module


###########################################################################
# load the sheep_net ethernet driver
###########################################################################

function load_sheep_net ()
{
    SHEEP_MOD=`get_mod_name sheep_net` || return $?
    [ $UID != 0 ] && {
	echo "*** Error: Must be root to load the sheep_net ethernet driver ***"
	return 1
    }

    echo -e "Loading SheepNet ethernet kernel module:\n   $SHEEP_MOD"
    /sbin/insmod -f $SHEEP_MOD 

    [ $? != 0 ] && return 1;

    [ ! -c /dev/sheep_net ] && {
	echo
	echo "WARNING: The ethernet interface /dev/sheep_net is missing."
	echo "Run 'mknod /dev/sheep_net c 10 198' as root to create it"
	echo
    }
}

# XXX: Before loading the module we should verify that it
# is really needed by checking /etc/molrc.

[ "$SHEEP_MOD_LOADED" != 1 ] && load_sheep_net 

[ "$_LOADONLY" == "1" ] && exit 0

# Run any user-specified start scripts

START_SCRIPT=`getres startscript`
[ "$START_SCRIPT" != "" ] && [ -f "$START_SCRIPT" ] && $START_SCRIPT start

###########################################################################
# detect common problems
###########################################################################

LOCKFILE=`getres -L`

[ -f $LOCKFILE ] && {
    ps `cat $LOCKFILE` | grep mol > /dev/null
    if [ $? != 0 ] ; then
	echo "Removing stale lockfile $LOCKFILE"
	rm -f $LOCKFILE
    else 
	echo "Mac-on-Linux is already running with pid `cat $LOCKFILE`"
	echo "according to the lockfile $LOCKFILE."
	exit 1;
    fi
}

getres -b enable_console_video && {
    [ ! -c /dev/fb0 ] && {
	echo "The framebuffer device /dev/fb0 is missing."
	echo "Run 'mknod /dev/fb0 c 29 0' to create it"
	exit 1
    }
    [ ! -f $MOL_LIB_DIR/config/vmodes ] && {
	echo
	echo "*************************************************************"
	echo " No video modes has been configured. Please run 'molvconfig'"
	echo " as root to setup full screen video or disable console"
	echo " video in the /etc/molrc file"
	echo "*************************************************************"
	exit 1
    }
}

###########################################################################
# run MOL in debugger?
###########################################################################

[ "$_DEBUG" == "1" ] && {
    $MOL_BINLIB_DIR/bin/mol $MOLARGS > /dev/null >& /dev/null
    $MOL_DBG $MOLARGS
    if [ $? -ne 2 ] ; then
	kill -HUP `cat $LOCKFILE`
    fi
    [ -f /etc/mol.sh ] && /etc/mol.sh stop
    exit 0
}


###########################################################################
# run MOL without debugger
###########################################################################

echo "Copyright (C) 1997-2001 Samuel Rydh <samuel@ibrium.se>"

# Never coredump (won't work anyway)
ulimit -c 0

$MOL_BINLIB_DIR/bin/mol $MOLARGS

# give MOL some time to create the lock file
x=0
while [ $x -le 6 ] ; do
    [ -f $LOCKFILE ] && break;
    usleep 500000
    x=`expr $x + 1`
done

[ -f $LOCKFILE ] && {
    MOLPID=`cat $LOCKFILE`
    trap "kill -INT $MOLPID" SIGINT

    while [ -d /proc/$MOLPID ] ; do
	sleep 3;
    done
    [ -f /etc/mol.sh ] && /etc/mol.sh stop
}

exit 0

