#!/bin/dash
#
# Start Fatdog on User Mode Linux
# Copyright (C) James Budiono 2013, 2014, 2018, 2019
# License: GNU GPL Version 3 or later.
#
# This script starts User Mode Linux virtual machine.
# Version 2 - for Fatdog64 700
# Version 3 - fight bitrot for Fatdog64 800
# Version 4 - support USE_NAT networking
#
# $1-path to uml-root; if not specified will create a "throwaway" vm
# $2-other options passed to kernel
# Note: uml-root must exist (even if it is empty). Otherwise it is ignored
#

### configuration

### modifiable settings with default values
RAM_SIZE=${RAM_SIZE:-256M}          # memory size, "M" must be used
SAVEFILE_SIZE=${SAVEFILE_SIZE:-128} # in MB, leave blank if no savefile is to be auto-created
SAVEFILE_FS=${SAVEFILE_FS:-ext2}    # type of the savefile to auto-create
SAVEFILE_PATH=/savefile.$SAVEFILE_FS # relative to UML_ROOT, leave blank to run without savefile
#HOST_IP=               # ip address, blank=use current ip address from $HOST_ADAPTER
#HOST_ADAPTER=          # if HOST_IP=blank, adapter to get ip address from, blank=use first active one
#UML_IP=                # ip address for UML, blank=use HOST_IP + 100
#USE_NAT=               # use NAT for network configuration
#START_CMD=             # command to execute on startup
WINDOW_SIZE=${WINDOW_SIZE-1024x600} # default window size for the virtual X server, blank=no virtual X server
NAME_SERVER=${NAME_SERVER:-8.8.8.8} # nameserver to use (default - use Google's)
#NICE_CMD=              # nice command for running UML kernel
#BOOT_OPTIONS=          # other boot options, e.g. earlyshell

### Fatdog hard-coded settings
AUFS_ROOT=/aufs
BASE_SFS_MOUNT=$AUFS_ROOT/pup_ro
BASELINE_MOUNT=$AUFS_ROOT/pup_init
BASE_SFS_DEVICE=${BASE_SFS_DEVICE:-/dev/loop1}
. $BOOTSTATE_PATH # AUFS_ROOT BASE_SFS_MOUNT BASELINE_MOUNT

BUSYBOX_SRC=$BASELINE_MOUNT/bin/busybox
DASH_SRC=$BASE_SFS_MOUNT/bin/dash
UML_SRC_DIR=/usr/lib64/uml
UML_INIT=uml-init
UML_SYSINIT="rc.*.uml"
UML_PATCHED="wmexit"
BB_LINKS="ash losetup mount umount cp rm ln ls mv cat mkdir chmod cmp
grep sed awk ifconfig route hostname setsid ps sleep halt cttyhack 
pivot_root chroot"
VMLINUX=$UML_SRC_DIR/vmlinux

# helper: $1-config file
dump_config() {
	for p in RAM_SIZE SAVEFILE_SIZE SAVEFILE_FS SAVEFILE_PATH \
	         HOST_IP HOST_ADAPTER UML_IP START_CMD WINDOW_SIZE \
			 NAME_SERVER NICE_CMD BOOT_OPTIONS; do
		eval echo $p=\\\'\$$p\\\'
	done > "$1"
}

cleanup() {
	[ $USE_NAT ] && iptables -t nat -D POSTROUTING -o $HOST_GW_DEV -j MASQUERADE
	[ $XSERVER_PID ] && kill $XSERVER_PID
	[ $THROWAWAY ] && rm -rf "$UML_ROOT"
	echo Finished.
}

# get random from $1 to $2 (inclusive)
get_random() {
	awk -v min=$1 -v max=$2 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'
}

# $1-ip, $2-which octet, $3-by how much
adjust_ipv4() {
	awk -F. -v ip=$1 -v octet=$2 -v howmuch=$3 \
'BEGIN {
	$0=ip
	$octet=($octet + howmuch) % 256
	printf("%d.%d.%d.%d", $1,$2,$3,$4);
}'
}


##################### main #######################
[ "$NOT_USING_AUFS" ] && exit 1
trap 'cleanup; exit' 0 INT HUP TERM

# check for help 
if [ "$1" = --help ] || [ "$1" = -h ]; then
	cat << EOF
Usage: $0 [/path/to/vm-root] [boot options]
If no path is given, a temporary VM session will be created in /tmp.
Path must exist (even if it is empty), otherwise it is ignored.
All other parameters after the first one is passed to the UML kernel.
If you want to pass boot options to temporary VM session, pass "" as path.
EOF
	exit
fi

# 0. checks - vmlinux exist, uml utilities exist, and BASE_SFS is readable
! [ -e $VMLINUX ] && echo "Please install UML kernel." && exit
! which uml_net > /dev/null && echo "Please install UML utilities." && exit
! [ -r $BASE_SFS_DEVICE ] && ! gtksu "Make $BASE_SFS_DEVICE readable" chmod o+r /dev/loop1 && exit

# 1. check if uml-root is specified, otherwise create one for throwaway sesssion
THROWAWAY=
if [ -z "$1" ] || ! [ -e "$1" ]; then
	echo "Creating a throwaway vm"
	UML_ROOT=$(mktemp -dp /tmp uml-vm-XXXXXXXX)
	THROWAWAY=yes
else
	# if config exist, load it
	[ -e "$1"/config ] && . "$1"/config
	UML_ROOT="$1"
	THROWAWAY=
	shift
fi
dump_config "$UML_ROOT"/config	# so that it can be modified later
BOOT_OPTIONS="$BOOT_OPTIONS $@"

# 2. validate and prepare rootfs if necessary
REBUILD= # do we need to rebuild the rootfs?
case "$@" in
	*rebuild*|*REBUILD*) REBUILD=1
esac

mkdir -p "$UML_ROOT"/dev # /dev so that devtmpfs is auto-mounted
if ! [ -e "$UML_ROOT"/bin/busybox ] || [ $REBUILD ]; then
	OLDPWD=$(pwd); cd "$UML_ROOT"
	mkdir -p bin sbin
	cp $BUSYBOX_SRC $DASH_SRC bin
	cp $UML_SRC_DIR/$UML_INIT sbin/init
		
	# prepare init files
	cp $UML_SRC_DIR/$UML_SYSINIT $UML_SRC_DIR/$UML_PATCHED .
	cat > inittab << EOF
# simplified busybox inittab for fatdog-uml
::sysinit:/etc/rc.d/rc.sysinit.uml
::respawn:/sbin/getty -n -l /bin/autologin 38400 console
::shutdown:/etc/rc.d/rc.shutdown
::shutdown:/etc/rc.d/rc.cleanup.uml
::restart:/bin/ash 2>&1
EOF

	cd bin; for p in $BB_LINKS; do ln -sf busybox $p 2>/dev/null; done
	cd "$OLDPWD"
fi

# 3. Auto-create savefile if not yet created (unless told otherwise or throwaway session)
if ! [ -e "$UML_ROOT"/"$SAVEFILE_PATH" ] && [ $SAVEFILE_SIZE ] && [ -z $THROWAWAY ]; then
	dd if=/dev/zero of="$UML_ROOT"/"$SAVEFILE_PATH" bs=1M count=0 seek=$SAVEFILE_SIZE
	mkfs -t $SAVEFILE_FS -F "$UML_ROOT"/"$SAVEFILE_PATH"
	echo Created $SAVEFILE_PATH of ${SAVEFILE_SIZE} MB filesystem: $SAVEFILE_FS
fi

# 4. set guest-ip/host-ip address unless already preset above
[ "$HOST_IP" ] || HOST_IP=$(ifconfig $HOST_ADAPTER | sed '/inet6/d; /inet/!d; /127.0.0/d; s/.*inet [^0-9]*//; s/[^0-9]*netmask.*//' | head -n 1)
if [ -z "$USE_NAT" ]; then
	# standard network: proxy arp
	[ "$UML_IP" ] || UML_IP=$(adjust_ipv4 $HOST_IP 4 $(get_random 1 255))
else
	# NAT network
	[ "$UML_IP" ] || UML_IP=$(adjust_ipv4 $HOST_IP 3 $(get_random 1 255))
	HOST_IP=$(adjust_ipv4 $UML_IP 4 $(get_random 1 255))
	HOST_GW_DEV=$(busybox iproute | awk '$1=="default" { print $5}' | head -n1)
	iptables -t nat -A POSTROUTING -o $HOST_GW_DEV -j MASQUERADE
fi

# 5. start X server
XSERVER_PID= UML_DISPLAY=
if [ "$WINDOW_SIZE" ]; then
	# get free display number
	for disp in $(seq 1 10); do
		[ ! -e /tmp/.X${disp}-lock ] && break
	done
	#Xnest -geometry 800x600 -ac :1 > /dev/null 2>&1 &
	UML_DISPLAY=$HOST_IP:$disp
	Xephyr :$disp -listen tcp -ac -host-cursor -screen $WINDOW_SIZE -keybd ephyr,,xkbmodel=evdev,xkbrules=evdev > /dev/null 2>&1 &
	XSERVER_PID=$!
fi

# 7. start the UML kernel (=start the VM)
#./vmlinux rootfstype=hostfs rw init=/bin/sh hostfs=./umlroot
#./vmlinux rootfstype=hostfs rw rootflags=$UML_ROOT mem=512M dsp=/dev/dsp mixer=/dev/mixer eth0=tuntap,,,$HOST_IP pfix=nox
$NICE_CMD $VMLINUX rootfstype=hostfs rw hostfs="$UML_ROOT" mem=$RAM_SIZE dsp=/dev/dsp mixer=/dev/mixer \
    SAVEFILE_PATH=$SAVEFILE_PATH ubd0rc=$BASE_SFS_DEVICE \
    eth0=tuntap,,,$HOST_IP HOST_IP=$HOST_IP UML_IP=$UML_IP NAME_SERVER=$NAME_SERVER \
    DISPLAY=$UML_DISPLAY START_CMD=$START_CMD $BOOT_OPTIONS
