#!/bin/bash
#
# \brief  Manage rumpkernel tools
# \author Josef Soentgen
# \date   2014-04-29
#


#
# Needed by glibc, otherwise loading modules as libraries for the
# rumpkernel does not work
#
export LD_DYNAMIC_WEAK=1


msg() {
	[ $ARG_VERBOSE -ge 0 ] && printf -- "${1}\n"
}


debug() {
	[ $ARG_VERBOSE -ge 2 ] && printf -- "\t${1}\n" > /dev/stderr
}


trap_exit() {
	if [ "$RUMP_SERVER" ]; then
		if [ $ARG_CRYPT_DEV -ne 0 ] && [ "$ARG_CRYPT_KEY" != "" ]; then
			rump_client "cgdconfig" "-u cgd0"
		fi
		rump_client "halt"
	fi

	[ -d "$RUMP_TMP" ] && rm -rf $RUMP_TMP
	pkill -P $$
}


trap_abort() {
	trap_exit
	[ -d "$RUMP_TMP" ] && rm -rf $RUMP_TMP
}


#
# Start the rumpkernel
#
# \param libs list of libraries (modules) the rumpkernel should
#             load on start-up
# \param disks list of drivespecs the rumpkernel should attach
#
rump_server() {
	local libs="$1"
	local disks="$2"

	$DRY_RUN $RUMP_PATH/rump_server $libs $disks $RUMP_SERVER
	if [ $? -ne 0 ]; then
		msg "could not start rump server."
		unset RUMP_SERVER
		exit 1
	fi
}


#
# Run a rumpkernel client
#
# \param cmd commando to execute within the rumpkernel
# \param args arguments for the commando
#
rump_client() {
	local cmd="$1"
	local args="$2"

	debug "$RUMP_PATH/$cmd $args"

	$DRY_RUN $RUMP_PATH/$cmd $args
	if [ $? -ne 0 ]; then
		echo "executing '$(basename $cmd) $args' failed."
		# XXX kill all processes that are still haning around
		# exit 1
	fi
}


#
# Encrypt or decrypt device
#
# If the key parameter is not blank decrypt the device. Otherwise
# generate a new key to encrypt.
#
# \param key key to encrypt
#
do_crypt_dev() {
	local key="$1"
	local args=

	if [ "$key" = "" ]; then
		args="-g -k storedkey -o /cgd0.conf aes-cbc 256"
	else
		args="cgd0 $RUMP_DISK_DEV /cgd0.conf"
	fi

	rump_client "cgdconfig" "$args"
}


#
# Format file system
#
# \param fs file system type
# \param dev raw device
#
do_format_fs() {
	local fs="$1"
	local dev="$2"
	local prog=
	local args=

	case $fs in
	ffs)    prog="newfs";;
	ext2fs) prog="newfs_ext2fs"; args="-I";;
	*)      msg "unsupported fs given."; return;;
	esac

	args="$args $dev"
	rump_client "$prog" "$args"
}


#
# List files in the file system
#
# \param mntpt mount point of file system in the rumpkernel namespace
#
do_list_fs() {
	local mntpt="$1"

	rump_client "ls" "-lRah $mntpt"
}


#
# Mount fs
#
# \param fs type of file system
# \param dev device to mount
# \param mntpt mount point
#
do_mount_fs() {
	local fs="$1"
	local dev="$2"
	local mntpt="$3"

	args="$dev $mntpt"

	# XXX maybe check if $mntpt exists
	rump_client "mkdir" "$mntpt"

	rump_client "mount_$fs" "$args"
}


#
# Umount fs
#
# \param mntpt mount point
#
do_umount_fs() {
	local mntpt="$1"

	rump_client "umount" "$mntpt"
}


#
# Populate fs
#
# \param top_dir root directory
#
do_populate_fs() {
	local top_dir="$(readlink -f $1)"
	local dirs="$(find $top_dir -mindepth 1 -type d|sed 's:'$top_dir'/::g')"
	local files="$(find $top_dir -type f|sed 's:'$top_dir'/::g')"

	#
	# First create the directories and copy the files thereafter
	#
	for dir in ${dirs[*]}; do
		rump_client "mkdir" "-p /mnt/$dir"
	done

	for file in ${files[*]}; do
		target_dir="$(dirname ${file})"

		rump_client "cp" "${RUMP_HOST_DIR}/${file} /mnt/${target_dir}"
	done
}


#
# Create cgdconfig(8) file from given key
#
# \param config_file name of the output file
# \param key key string
#
create_cgd_config() {
	local config_file=$1
	local key=$2

	if [ "$key" != "" ]; then
		$TOOL_DIR/rump_cgdconf -k $key > $config_file
	else
		echo > $config_file
	fi

	chmod 600 $config_file
}


#
# Create temporary directory
#
# If the dir parameter is not set, create a temp directory automagically.
#
# \param dir path to the directory
#
create_temp_dir() {
	local dir="$1"

	if [ "$dir" != "" ]; then
		RUMP_TMP="$dir"
		mkdir $RUMP_TMP
	else
		RUMP_TMP=$(mktemp -d -t genode-rump.XXXXXX 2>&1)
		[ $? -ne 0 ] && { echo 'could not create temp dir'; exit 1; }
	fi
}


#
# Print usage
#
print_usage() {
	local help="$1"

	printf "usage: $PROG_NAME [-cfhlnvH] [-d dev] [-F fs] [-k key] "
	printf "[-p dir] [-t dir] <disk_image>\n"
	if [ "$help" != "" ]; then
		printf "\t-c       use cgd(4) to en/decrypt image\n"
		printf "\t-f       format image (see -F)\n"
		printf "\t-h       print this help screen\n"
		printf "\t-l       list files in disk_image\n"
		printf "\t-n       dry run, print commands w/o executing\n"
		printf "\t-v       verbose mode, may be used multiple times\n"
		printf "\t-d dev   device which is used for formatting within "
		printf "the rumpkernel\n"
		printf "\t         [e.g. /blk_device or /dev/rcgd0a]\n"
		printf "\t-F fs    specify file system [ffs, ext2fs]\n"
		printf "\t-k key   use given key to access cgd(4) device\n"
		printf "\t-p dir   populate the image with the content of "
		printf "dir\n"
		printf "\t-t dir   temp directory for storing rump tool "
		printf "files [/tmp/genode-rump.XXXXXX]\n"
	fi
}


#
# Parse commandline arguments
#
parse_arguments() {
	args=$(getopt cd:fhk:lnp:t:vHF: ${*})
	[ $? != 0 ] && exit 1
	if [ $# -lt 1 ]
	then
		print_usage
		exit 1
	fi
	set -- $args
	while [ $# -ge 0 ]; do
		case "$1" in
		-c) ARG_CRYPT_DEV=1; shift;;
		-d) ARG_DISK_DEV="$2"; shift; shift;;
		-f) ARG_FORMAT_DEV=1; shift;;
		-F) ARG_FORMAT_FS="$2"; shift; shift;;
		-h) print_usage "help"; exit 0;;
		-k) ARG_CRYPT_KEY="$2"; shift; shift;;
		-l) ARG_LIST_FS=1; shift;;
		-n) ARG_DRY_RUN=1; shift;;
		-p) ARG_POPULATE_FS="$2"; shift; shift;;
		-t) ARG_TMP_DIR="$2"; shift; shift;;
		-v) ARG_VERBOSE=$(($ARG_VERBOSE + 1)); shift;;
		--) shift; break;;
		esac
	done

	if [ $# -lt 1 ]; then
		msg "aborting, disk image missing."
		exit 1
	fi

	ARG_DISK_IMAGE="$1"
	if [ ! -f "$ARG_DISK_IMAGE" ]; then
		msg "aborting, could not open '$ARG_DISK_IMAGE'."
		exit 1
	fi

	if [ $ARG_LIST_FS -eq 1 ] && [ $ARG_FORMAT_DEV -eq 1 ]; then
		msg "aborting, -f and -l are mutually exclusive."
		exit 1
	fi

	if [ "$ARG_CRYPT_KEY" != "" ] && [ "$ARG_DISK_DEV" = "" ]; then
		ARG_DISK_DEV="/dev/cgd0a"
	fi

	[ "$ARG_DISK_DEV" != "" ] && RUMP_FORMAT_DISK_DEV="$ARG_DISK_DEV"

	[ $ARG_DRY_RUN -eq 1 ] && DRY_RUN=echo

	if [ $ARG_FORMAT_DEV -eq 1 ] && [ "$ARG_FORMAT_FS" = "" ]; then
		msg "aborting, filesystem type not specified."
		exit 1
	fi
}


main() {
	local disk_dev=

	parse_arguments "$@"

	create_temp_dir "$ARG_TMP_DIR"

	export RUMP_SERVER="unix://$RUMP_TMP/server_socket"

	#
	# First we prepare the actions...
	#
	if [ "$ARG_FORMAT_FS" != "" ]; then
		RUMP_LIBS="$RUMP_LIBS -lrumpfs_$ARG_FORMAT_FS"
		# XXX
		[ "$ARG_FORMAT_FS" = "ext2fs" ] && RUMP_LIBS="$RUMP_LIBS -lrumpfs_ffs"
	fi

	if [ $ARG_FORMAT_DEV -eq 1 ]; then
		# only add the disk image as raw device if do not
		# already have another device
		[ "$ARG_DISK_DEV" = "" ] && disk_dev="$disk_dev -d key=$RUMP_DISK_DEV,hostpath=$ARG_DISK_IMAGE,size=host,type=chr"
	else
		[ "$ARG_DISK_DEV" = "" ] && disk_dev="$disk_dev -d key=$RUMP_DISK_DEV,hostpath=$ARG_DISK_IMAGE,size=host,type=blk"
	fi

	if [ $ARG_CRYPT_DEV -eq 1 ]; then
		RUMP_LIBS="$RUMP_LIBS -lrumpkern_crypto -lrumpdev_cgd -lrumpdev_rnd"
		disk_dev="-d key=$RUMP_DISK_DEV,hostpath=$ARG_DISK_IMAGE,size=host,type=blk"

		cgd_config="$RUMP_TMP/cgd0.conf"
		create_cgd_config "$cgd_config" "$ARG_CRYPT_KEY"
		disk_dev="$disk_dev -d key=/cgd0.conf,hostpath=$cgd_config,size=host,type=reg"
	fi

	if [ "$ARG_POPULATE_FS" != "" ]; then
		disk_dev="$disk_dev -d key=$RUMP_HOST_DIR,hostpath=$ARG_POPULATE_FS,size=host,type=dirs"
	fi

	#
	# ... and start the rump server...
	#
	rump_server "$RUMP_LIBS" "$disk_dev"

	#
	# ... then we execute the actions.
	#
	if [ $ARG_CRYPT_DEV -eq 1 ]; then
		do_crypt_dev "$ARG_CRYPT_KEY"

		if [ "$ARG_CRYPT_KEY" = "" ]; then
			$TOOL_DIR/rump_cgdconf -f "$cgd_config"
		fi
	fi

	if [ $ARG_FORMAT_DEV -eq 1 ]; then
		do_format_fs "$ARG_FORMAT_FS" "$RUMP_FORMAT_DISK_DEV"
	fi

	if [ "$ARG_POPULATE_FS" != "" ]; then
		do_mount_fs "$ARG_FORMAT_FS" "$RUMP_FORMAT_DISK_DEV" "/mnt"
		do_populate_fs "$ARG_POPULATE_FS" "/mnt"
		do_umount_fs "/mnt"
	fi

	if [ $ARG_LIST_FS -eq 1 ]; then
		do_mount_fs "$ARG_FORMAT_FS" "$RUMP_FORMAT_DISK_DEV" "/mnt"
		do_list_fs "/mnt"
		do_umount_fs "/mnt"
	fi

	exit 0
}


PROG_NAME=$(basename $0)
ARG_CRYPT_DEV=0
ARG_FORMAT_DEV=0
ARG_DRY_RUN=0
ARG_FORMAT_FS=""
ARG_LIST_FS=0
ARG_TMP_DIR=""
ARG_VERBOSE=0

DRY_RUN=""

TOOL_DIR=$(dirname $(readlink -f $0))
GENODE_DIR=$(readlink -f $TOOL_DIR/..)

RUMP_TMP=""
RUMP_DIR="/usr/local/genode-rump"
RUMP_PATH="$RUMP_DIR/bin"
RUMP_LIBS="-lrumpdev -lrumpdev_disk -lrumpvfs"
RUMP_SERVER=""
RUMP_DISK_DEV=/disk_image
RUMP_FORMAT_DISK_DEV="$RUMP_DISK_DEV"
RUMP_HOST_DIR=/host

trap "trap_abort" INT TERM
trap "trap_exit" EXIT

main "$@"

exit 0

# End of file