#!/bin/bash
# Copyright (C) 2006 Nokia Corporation
#
# This is proprietary software owned by Nokia Corporation.
# 
# Contact: Maemo Integration <integration@maemo.org>

__self=`basename $0`

# Release configuration
__maemo_version=4.0alpha
__maemo_release=bora
__target_toolchain=cs2005q3.2-glibc

# Target configuration for armel
__armel_toolchain=${__target_toolchain}-arm
__armel_devkits=perl:debian-sarge:maemo3-debian:maemo3-tools:cputransp
__armel_cputransp=qemu-arm-0.8.2-sb2

# Target configuration for i386
__i386_toolchain=${__target_toolchain}-i386
__i386_devkits=perl:debian-sarge:maemo3-debian:maemo3-tools:doctools

# Scratchbox 
__scratchbox_version=1.0.7
__scratchbox_require=1.0.7
__scratchbox_release=apophis
# FIXME Only removes first linefeed from line
# __scratchbox_devkits=`echo "$__armel_devkits $__i386_devkits" | sed "s,:,\n,g" | sort -u | sed "s,\n,\ ,g"`

# Defaults
__default_group=sbox
__default_scratchbox=/scratchbox

# Download path
__download_source_tarball=http://scratchbox.org/download/files/sbox-releases/branches/apophis/r4/tarball
__download_source_deb=http://scratchbox.org/download/files/sbox-releases/branches/apophis/r4/deb
__download_dest=/tmp
__scratchbox_files_tgz="scratchbox-core-1.0.7-i386.tar.gz scratchbox-devkit-cputransp-1.0.2-i386.tar.gz scratchbox-devkit-debian-1.0.8-i386.tar.gz scratchbox-devkit-doctools-1.0.6-i386.tar.gz scratchbox-devkit-perl-1.0.4-i386.tar.gz scratchbox-devkit-maemo3-1.0-i386.tar.gz scratchbox-libs-1.0.7-i386.tar.gz scratchbox-toolchain-cs2005q3.2-glibc-arm-1.0.5-i386.tar.gz scratchbox-toolchain-cs2005q3.2-glibc-i386-1.0.5-i386.tar.gz"
__scratchbox_files_deb="scratchbox-core_1.0.7_i386.deb scratchbox-devkit-cputransp_1.0.2_i386.deb scratchbox-devkit-debian_1.0.8_i386.deb scratchbox-devkit-doctools_1.0.6_i386.deb scratchbox-devkit-perl_1.0.4_i386.deb scratchbox-devkit-maemo3_1.0_i386.deb scratchbox-libs_1.0.7_i386.deb scratchbox-toolchain-cs2005q3.2-glibc-arm_1.0.5_i386.deb scratchbox-toolchain-cs2005q3.2-glibc-i386_1.0.5_i386.deb"


# Shows usage text
function usage() {
	cat <<EOF
Usage: $__self [OPTIONS]

Installer for Scratchbox $__scratchbox_version '$__scratchbox_release' release.
Scratchbox is a cross-compilation toolkit for Linux. This script installs
version which is suited for maemo $__maemo_version '$__maemo_release' release development.
This script cannot be used to update an existing installation.

 * Checks for compatibility of your Linux environment.
 * Downloads scratchbox packages from scratchbox.org
 * Installs Scratchbox to your computer

On Debian-based distributions, the toolkit will be installed per default
from Debian .deb packages. Packages install to default path /scratchbox .

On any other Linux distribution: the install will use .tar.gz files
which can be installed to any given path which ends in /scratchbox
e.g. to /scratchbox or /opt/scratchbox .


Options:
	-v	Display version and exit.
	-h	Show this usage guide.
	-c	Use existing downloads, don't try to download again.
	-d	Install from Debian dpkg packages.
	-s PATH	Install from .tar.gz to path (to e.g. $__default_scratchbox).
	-g NAME	Specify Scratchbox user group (default $__default_group)
	-u USER	Specify users to add to Scratchbox (separated by commas).

EOF
}

# Show version information
function version() {
	echo "Installer for Scratchbox $__scratchbox_version '$__scratchbox_release' release."
}

# Fail in case str is empty and print out message
# empty (str,msg)
function empty () {
	local str=$1
	local message=$2
	if [ "x$str" = "x" ] ; then
		echo "E: Program error. $message"
		echo "E: If this problem persists please report to <integration@maemo.org>"
		exit 1
	fi
}

# Downloads file in url given with parameter
# download (outdir,url)
#	outdir must be non-empty
#	url must be non-empty
function download() {
	local outdir=$1
	local url=$2
	local file=`basename $url`
	empty "$outdir" "Download output directory string must be non-empty."
	empty "$url"  "Download URI string must be non-empty."
	# TODO Add timeout to file download
	wget -nd -P $outdir $url
	if [ $? != 0 ] ; then
		echo "E: Couldn't retrieve file '$file'."
		exit 1
	fi
	# TODO Check that downloaded file is readable from path
}

# Test if string ends with given match
# string_endswith (string,match)
#	string must be non-empty
#	match must be non-empty
function string_endswith() {
	local string=$1
	local match=$2
	empty "$string" "String to be checked must be non-empty."
	empty "$match" "Match to be checked must be non-empty."
	echo "$string" | grep -q "$match\$"
	return $?
}

# Run gunzip archive integrity check on file
# test_integrity (file)
#	file must be non-empty
function test_integrity() {
	local file=$1
	empty "$file" "File to be checked must be non-empty."
	# deb
	if string_endswith "$file" ".deb" ; then
		# control file present
		ar p $file control.tar.gz | gunzip -qt 2>/dev/null
		if [ $? -ne 0 ] ; then
			return $?
		fi
		# data file present
		ar p $file data.tar.gz | gunzip -qt 2>/dev/null
		if [ $? -ne 0 ] ; then
			return $?
		fi
		# debian-binary file present
		ar p $file debian-binary >/dev/null
		return $?
	fi
	# tar.gz
	if string_endswith "$file" ".tar.gz" ; then
		gunzip -qt $file 2>/dev/null
		return $?
	fi
	# don't know how to check
	echo "E: Don't know how to check file '$file'."
	exit 1
}

# Taken from earlier maemo installer
# This function takes two version number strings as an argument.
# Return value is
#   0, if the version numbers are identical
#   1, if the first version number is greater
#   2, if the second version number is greater
# TODO Check that arguments lhs and rhs are in the form DIGIT.DIGIT.DIGIT
# compare_version_numbers (lhs,rhs)
function compare_version_numbers() {
	local lhs=$1
	local rhs=$2
	local c=1
	while [ 1 ]; do
		V1=`echo $lhs | cut -d . --fields $c`
		V2=`echo $rhs | cut -d . --fields $c`
		LEN1=`echo $V1 | wc -c`
		LEN2=`echo $V2 | wc -c`

		if [[ "$LEN1" -le "1" && "$LEN2" -le "1" ]]; then
			return 0
		fi
		if [[ "$LEN2" -le "1" || "$V1" -gt "$V2" ]]; then
			return 1
		fi
		if [[ "$LEN1" -le "1" || "$V1" -lt "$V2" ]]; then
			return 2
		fi
		c=$((c+1))
	done
}

# Test if running this script on Debian or derivative distribution
# has_debian ()
function has_debian () {
	if [ -r /etc/debian_version ] ; then
		return 1
	fi
	return 0
}

# Scratchbox installation has toolchain installed
# TODO Add more toolchain sanity tests
# has_toolchain (scratchbox,toolchain)
# 	toolchain must be non-empty
function has_toolchain() {
	local scratchbox=$1
	local toolchain=$2
	empty "$toolchain" "Scratchbox toolchain string must be non-empty."
	if [ -d $scratchbox/compilers/$toolchain ] ; then
		return 0
	fi
	return 1
}

# Scratchbox installation has devkit installed
# TODO Add more devkit sanity tests
# TODO Required devkit minimum version check
# has_devkit (scratchbox,devkit)
# 	devkit must be non-empty
function has_devkit() {
	local scratchbox=$1
	local devkit=$2
	empty "$devkit" "Scratchbox devkit string must be non-empty."
	if [ -d $scratchbox/devkits/$devkit ] ; then
		return 0
	fi
	return 1
}

# Scratchbox installation has sessions running 
# has_sessions (scratchbox)
function has_sessions() {
	local scratchbox=$1
	if ! $scratchbox/tools/bin/sb-conf list --targets 2>&1 | grep -q "^sbrsh-conf: No current target$" 1>/dev/null; then
		if [ `$scratchbox/tools/bin/sb-conf list --sessions | wc -l` -gt 1 ] ; then
			return 0
		fi
	fi
	return 1
}

# Download a file from url to dest directory
# get_file (url, dest, cached, description)
function get_file () {
	local url=$1
	local dest=$2
	local cached=$3
	local file=`basename $url`
	local destfile=$dest/$file
	empty "$dest" "Download output directory string must be non-empty."
	empty "$url"  "Download URI string must be non-empty."
	# Remove files downloaded earlier (default yes)
	if [ "x$cached" != "xyes" ] && [ -r $destfile ] ;  then
		echo "Removed earlier file '$file'."
		rm -f $destfile
	fi 
	# Download file found from URI if not already found
	if [ ! -r  $destfile ] ; then
		echo "Downloading '$file'."
		download $dest $url
	else
		echo "Using previously downloaded '$file'."
	fi
}

# Prints out an underlined text banner
# phase (text)
function phase () {
	local text=$1
	local underline=`echo $text | sed 's,.,-,g'`
	echo
	echo "$text"
	echo "$underline"
	echo
}

# Main
# TODO Add long command line options
while getopts "hvcds:g:u:" opt ; do
	case "$opt" in
		h)
			usage 
			exit 0
			;;
		v)
			version 
			exit 0
			;;
		c)
			__cached=yes 
			;;
		d)
			__type=deb
			__scratchbox=$__default_scratchbox
			;;
		s)
			__type=tgz
			__scratchbox=$OPTARG 
			;;
		g)
			__scratchbox_group=$OPTARG
			;;
		u)
			__users=$OPTARG
			;;
		[?])
			usage
			exit 1
			;;
	esac
done

# Runtime options
if [ -z $__scratchbox ] ; then
	echo "E: Give installation path using '-s PATH' argument."
	usage
	exit 1
fi

# Set Debian package defaults
if [ -z $__type ] ; then
	if has_debian ; then
		__type=deb
	else
		__type=tgz
	fi
fi

# Scratchbox group defaults
if [ -z $__scratchbox_group ] ; then
	__scratchbox_group=$__default_group
fi

# Use cached file default
if [ -z $__cached ] ; then
	__cached=no
fi

# Set download path
if [ $__type == "tgz" ] ; then
	__download_source=$__download_source_tarball
else
	__download_source=$__download_source_deb
fi

# Set downloadable files
if [ $__type == "tgz" ] ; then
	__scratchbox_files=$__scratchbox_files_tgz
else
	__scratchbox_files=$__scratchbox_files_deb
fi

echo "This script will install Scratchbox $__scratchbox_version '$__scratchbox_release' release to your computer."

phase "Install options"

cat <<END
Install from packages=$__type
Scratchbox install path=$__scratchbox
Scratchbox group=$__scratchbox_group
armel compiler=$__armel_toolchain
i386 compiler=$__i386_toolchain
armel devkits=$__armel_devkits
i386 devkits=$__i386_devkits
armel CPU transparency=$__armel_cputransp
END

phase "Checking for prerequisites"

# Check not run as user root
echo "Running as user root..."
if [ $UID != 0 ] ; then
	echo "E: This script must be run as user root."
	exit 1
fi

# Check for fakeroot
echo "Running as user root inside fakeroot..."
if [ ! -z "$FAKEROOTKEY" ] ; then
	echo "E: This script should not be run inside fakeroot."
	exit 1
fi

# Check for running inside scratchbox
echo "Running outside of scratchbox..."
if [ -r /targets/links/scratchbox.config ] ; then
	echo "E: This script needs to be run outside of scratchbox."
	exit 1
fi

# Check for running on Linux
echo "Running on Linux kernel..."
if [ `uname -o` != "GNU/Linux" ] ; then
	echo "E: Currently Scratcbox can only run under Linux."
	exit 1
fi

# Check for running on i386
echo "Running on i386 architecture..."
__uname_m=`uname -m`
if [ `expr match "$__uname_m" "i[3-6]86"` != 4 ] ; then
	echo "E: Currently Scratcbox can only run in 32 bit i386 architecture."
	exit 1
fi

# Kernel has the binfmt_misc module
echo "Host kernel binfmt_misc support..."
if [ ! -d "/proc/sys/fs/binfmt_misc" ]; then
	echo "E: Host kernel module binfmt_misc is required for the CPU transparency feature."
	exit 1
fi

# VDSO support is off
# TODO Not tested
echo "Host kernel VDSO support..."
if [ -f "/proc/sys/kernel/vdso" ] && [ `cat /proc/sys/kernel/vdso` -eq 1 ] ; then
	echo "E: Host kernel VDSO support is incompatible with scratchbox." 
	echo "E: You can disable VDSO support with 'echo 0 > /proc/sys/kernel/vdso' as root"
	echo "E: or by adding 'vdso=0' to the kernel parameters."
	exit 1
fi

# SELinux must be off
# TODO Not tested
echo "Host kernel SELinux extensions..."
if [ -f "/selinux/enforce" ] && [ `cat /selinux/enforce` -eq 1 ] ; then
	echo "E: Scratchbox cannot be used under Security Enchanced Linux (SELinux)."
	echo "E: Change to permissive mode with 'echo 0 > /selinux/enforce' as root."
	exit 1
fi


# Host kernel local IPv4 port range...
# TODO Not tested
echo "Host kernel local IPv4 port range..."
if [ `cat /proc/sys/net/ipv4/ip_local_port_range | awk '{ print ($2-$1) }'` -lt 10000 ] ; then 
	echo "E: Host kernel has IPv4 local port range under 10000."
	echo "E: This causes problems with fakeroot."
	echo "E: Increase with 'echo \"1024 65000\" > /proc/sys/net/ipv4/ip_local_port_range'"
	exit 1
fi

# Check for wget
__wget=`which wget`
echo "Which wget tool in path..."
if [ -z $__wget ] || [ ! -x $__wget ] ; then
	echo "E: This script requires wget to download rootstraps and installer files."
	echo "E: On most Linux distributions this is provided by 'wget' package."
	exit 1
fi

# Debian dpkg found and executable
if [ $__type == "deb"  ] ; then
	__dpkg=`which dpkg`
	echo "Which dpkg tool in path..."
	if [ -z $__dpkg ] || [ ! -x $__dpkg ] ; then
		echo "E: This script requires dpkg to install .deb files."
		exit 1
	fi
fi

# Scratchbox install path is sane
echo "Scratchbox install path is sane..."
if [ `expr match "$__scratchbox" '/'` -ne 1 ] ; then
	echo "E: Scratchbox install path must start with '/'."
	exit 1
fi
if ! string_endswith "$__scratchbox" "/scratchbox" ; then
	echo "E: Scratchbox install path must end with '/scratchbox'."
	exit 1
fi


# Check for scratchbox installation
echo "Scratchbox installation not existing..."
if [ -d $__scratchbox ] ; then
        echo "E: Scratchbox already found in installation path '$__scratchbox'."
        echo "E: Please remove your scratchbox installation first if you want to proceed."
	echo "E: Remember to copy your scratchbox users' home directories." 
	echo "E: Also run '$__scratchbox/sbin/sbox_ctl stop' before removing directory."
        echo "E: Specify an alternative installation path using '-s PATH' option."
        exit 1
fi


# Users given as parametre
echo -n "Scratchbox user names... "
for __user in `echo "$__users" | sed "s/,/ /g"` ; do
	if [ -z "$__user" ] ; then
		echo
		echo "E: Empty username in scratchbox users list."
		exit 1
	fi
	echo -n "$__user "
	getent passwd "$__user" 2>/dev/null 1>&2
	if [ $? -ne 0 ] ; then
		echo
		echo "E: User '$__user' not found with 'getent passwd $__user' command."
		exit 1
	fi
done
echo

echo "Everything seems ok."

phase "Downloading scratchbox packages"

# Download and run scratchbox from packages
for __file in  ${__scratchbox_files} ; do
	get_file ${__download_source}/${__file} ${__download_dest} ${__cached}
	if ! [ -r ${__download_dest}/${__file} ] ; then
		echo "E: Downloaded file '$__file' not found from download path."
		exit 1
	fi
	if ! test_integrity ${__download_dest}/${__file} ; then
		echo "E: Downloaded file '$__file' failed integrity test."
		exit 1
	fi
done

phase "Setting up"

# Install packages
if [ $__type == "deb"  ] ; then
	# Install dpkg packages
	echo "Installing from Debian packages."
	__install_files=""
	for __file in  ${__scratchbox_files} ; do
		 __install_files="$__install_files ${__download_dest}/${__file}"
	done
	dpkg -i $__install_files
fi
if [ $__type == "tgz"  ] ; then
	# Extract .tar.gz packages
	__extractdir=`dirname ${__scratchbox}`
	mkdir -p ${__extractdir}
	for __file in  ${__scratchbox_files} ; do
		echo "Extracting '$__file' to '$__scratchbox'."
		tar -C ${__extractdir} -xzf ${__download_dest}/${__file}
	done
	echo
	# Run first time script
	echo "Running first time script."
	if [ ! -x $__scratchbox/run_me_first.sh ] ; then
		echo "E: Scratchbox first time script is not executable."
		echo "E: Something went wrong with the install. Sorry."
		exit 1
	fi
	cat >$__scratchbox/run_me_first.commands << END
no
$__scratchbox_group
yes
END
	$__scratchbox/run_me_first.sh < $__scratchbox/run_me_first.commands
	rm -Rf __scratchbox/run_me_first.commands
fi

# Some small sanity checks
for __file in  $__scratchbox/sbin/sbox_adduser $__scratchbox/sbin/sbox_ctl $__scratchbox/login $__scratchbox/compilers/bin/gcc ; do
	if [ ! -x $__file ] ; then
		echo "E: Scratchbox command '$__file' is not executable."
		echo "E: Something went wrong with the install. Sorry."
		exit 1
	fi
done

# Test if devkits are present
# FIXME Add test when devkits string is fixed
#for __devkit in $__scratchbox_devkits ; do 
#	if ! has_devkit $__devkit ; then
#		echo "E: Devkit not found. Installation not complete. Exiting."
#		exit 1
#	fi
#done

# Add users to scratchbox group
for __user in `echo "$__users" | sed "s/,/ /g"` ; do
	echo "Adding scratchbox user '$__user'."
	$__scratchbox/sbin/sbox_adduser "$__user" yes
	if [ $? != 0 ] ; then
		echo "E: Adding scratchbox user failed. Installation not complete. Exiting."
		exit 1
	fi
done

phase "Installation was successful!"

cat <<END
You now have Scratchbox $__scratchbox_version '$__scratchbox_release' release installed.

Scratchbox cannot be run as user root. Instead, use your normal login
user account. Add additional scratchbox users and sandboxes with the
following command (outside scratchbox with root permissions):

	# $__scratchbox/sbin/sbox_adduser USER yes

Running this command will create sandbox environment for that user and
add user to the '$__scratchbox_group' scratchbox user group.
You will need to start a new login terminal after being added to the
'$__scratchbox_group' group for group membership to be effective.

END

# On Debian postinst scripts take care of this automagically
if [ $__type == "tgz" ] ; then
	cat <<END
Scratchbox service must be started for CPU transparency to be functional.
Run the following command (outside scratchbox with root permissions):

	# $__scratchbox/sbin/sbox_ctl start

Add this command to e.g. /etc/rc.local file to start scratchbox service
at boot time.

END
fi

cat <<END
Login to scratchbox session using the following command (as user):

	$ $__scratchbox/login

Refer to scratchbox.org documentation for more information re scratchbox:
http://scratchbox.org/documentation/user/scratchbox-1.0/

END
