#! /bin/bash
set -e

[ "${VSERVER_ADMIN_DEBUG}" ] || VSERVER_ADMIN_DEBUG=0
declare -r VSERVER_ADMIN_DEBUG

if [[ ${VSERVER_ADMIN_DEBUG} -gt 2 ]] ; then
	set -x
fi

__vsa_bindir=$(dirname "$0")

# Obtain full paths with a dirty trick :)
#
__vsa_full_path ()
{
	local base='' path=$1

	if [[ ! -d $1 ]] ; then
		base="/${1##*/}"
		path=${1%/*}
	fi

	pushd "${path}" > /dev/null
	path=$(pwd)
	popd > /dev/null

	echo "${path}${base}"
}


# Get full bindir
#
__vsa_bindir=$(__vsa_full_path "${__vsa_bindir}")

# Configuration file
#
__vsa_config_file=${VSERVER_ADMIN_CONF:-/etc/admintools/vserver-admin.conf}

# Mark some variables as read-only
declare -r __vsa_bindir __vsa_config


# Utility functions which actions can use
#
__vsa_usage_and_exit ()
{
	local usage line impl desc

	impl="${__vsa_bindir}/vserver-admin-${__vsa_action}"
	usage=$(sed -e '/^##:/p' -e d "${impl}")
	desc=$(sed -e '/^##-/p' -e d "${impl}")

	echo "Usage: vserver-admin ${__vsa_action}${usage#\#\#:}"
	echo "  ${desc#\#\#-}"
	echo
	echo "Arguments:"
	while read -r line ; do
		echo "  ${line#\#\#+}"
	done < <( sed -e '/^##+/p' -e d "${impl}" )
	echo
	exit 1
}


# Prints a message and exits with an error
#
__vsa_die ()
{
	[ "$*" ] && echo "vserver-admin-${__vsa_action}: $*" >&2
	exit 2
}


# Checks for a valid IP address
#
__vsa_validate_ipaddr ()
{
	if [[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
	then
		local old_IFS=${IFS}

		IFS='.'
		local -a ip=( $ip )
		IFS=${old_IFS}

		if [[ ${ip[0]} -le 255 \
			 && ${ip[1]} -le 255 \
			 && ${ip[2]} -le 255 \
			 && ${ip[3]} -le 255 ]]
		then
			return 0
		fi
	fi

	# If we reach here, then something failed
	__vsa_die "Invalid IP addredd '$1'"
}



# Picks and "[iface:]ipaddr[/mask]" argument, with optional iface and
# mask, and gets the full "iface:ipaddr/mask"
#
# Usage: __vsa_guess_vnet address [default_iface [default_mask]]
#
# If default_iface is not given, then dst_net_interface is used.
# If default_mask  is not given, then dst_net_mask_bits is used.
#
__vsa_guess_vnet ()
{
	local iface maskbits ipaddr=$1

	iface=${1%:*}
	if [[ ${iface} = $1 ]] ; then
		iface=${2:-${dst_net_interface}}
	else
		ipaddr=${ipaddr#${iface}:}
	fi

	maskbits=${1#*/}
	if [[ ${maskbits} = $1 ]] ; then
		maskbits=${3:-${dst_net_mask_bits}}
	else
		ipaddr=${ipaddr%/${maskbits}}
	fi

	__vsa_validate_ipaddr "${ipaddr}"

	echo "${iface}:${ipaddr}/${maskbits}"
}


# Waits for some node to appear in the filesystem -- useful to wait for
# entries in /dev to appear after creating LVM volumes, for example.
# This will retry for cheking that the given path is readable, waiting 0.2
# seconds between tries, 5 times.
#
# Usage: __vsa_wait_fs /dev/vg/foo
#
__vsa_wait_fs ()
{
	local -i count=${2:-5}
	while [[ ! -r $1 ]] ; do
		if [[ ${VSERVER_ADMIN_DEBUG} -gt 0 ]] ; then
			echo "=== Waiting for $1 (tries left: ${count})"
		fi
		if [[ $(( --count )) -le 0 ]] ; then
			__vsa_die "Device '$1' not available"
		fi
		# Wait for 200ms
		sleep 0.200
	done
}




# Runs a command, if VSERVER_ADMIN_DEBUG is set to a non-empty value, then
# commands are printed out before executing them. Otherwise, standard output
# of commands is silently sent to /dev/null.
#
__vsa_do ()
{
	if [[ ${VSERVER_ADMIN_DEBUG} -gt 0 ]] ; then
		echo "*** $*"
		if [[ ${VSERVER_ADMIN_DEBUG} -lt 2 ]] ; then
			"$@"
		fi
	else
		"$@" > /dev/null
	fi
}


# Checks whether vServer files and stuff is present in the system
#
__vsa_sanity_check ()
{
	type -P vserver &> /dev/null || return 1
	[[ -d /etc/vservers ]]       || return 1
	[[ -d /proc/virtual ]]       || return 1

	# Check sane permissions
	local perm=$(stat --format='%a' "${__vsa_vdir}")
	[[ ${perm} = 700 ]] || return 1
}


# Obtain lock
#
__vsa_run_locked ()
{
	local name="/var/lock/vserver-admin.lock"
	local link="${name}.held"

	touch "${name}"
	if ! ln "${name}" "${link}" &> /dev/null ; then
		__vsa_die "Coult not acquire lock '${link}'"
	fi
	trap "rm -f '${link}'" EXIT
}


# Monstruous sed command chain used by the configuration file loader...
#
__vsa_config_sed='
/^_=/d
/^PWD=/d
/^IFS=/d
/^PS.=/d
/^BASH/d
/^TERM=/d
/^PATH=/d
/^SHELL/d
/^SHLVL=/d
/^GROUPS=/d
/^E\\?UID=/d
/^P\\?PID=/d
/^DIRSTACK=/d
/^PIPESTATUS=/d
/^OPT\(ERR\|IND\)=/d
/^\(OS\|MACH\)TYPE=/d
/^HOST\(NAME\|TYPE\)=/d
s/^/%s_/
'


# Loads the configuration file
#
# XXX Be careful when trying to understand this, heavy shell-fu follows
#     for cleaning up the environment thus the config file is sourced in
#     a sandboxed way.
#

__vsa_config ()
{
	[[ -r ${__vsa_config_file} ]] \
		|| __vsa_die "Configuration file '${__vsa_config_file}' not readable"

	# Modify the sed script above to include the user-defined prefix
	#
	local cfg_sed=$(printf "${__vsa_config_sed}" "${2:-cfg}")

	# Pick the input into a variable, plus add a "declare" statement at the
	# end so variables will be dumped after all defines have been done.
	#
	local cfg_input="$(< "${__vsa_config_file}") ; declare"

	# Loading the configuration file works this way:
	#
	#  1. "env -" clears the environment
	#  2. We define the variables for which substitutes are allowed in config
	#  3. We run the above input in a subshell launched from "env"
	#  4. Then "sed" uses the above script to filter out things we do not want
	#  5. The resulting script is eval'd -- but we save it first to a variable
	#     so we can print it when debugging is requested.
	#
	local cfg_script="$(env -      \
	        vserver=$1              \
	        hostname=${HOSTNAME}     \
	        fsroot="${__vsa_vdir}/$1" \
	    "${SHELL}" <<< "${cfg_input}" | sed "${cfg_sed}")"

	if [[ ${VSERVER_ADMIN_DEBUG} -gt 0 ]] ; then
		echo ">>> __vsa_config $*"
		echo "${cfg_script}"
		echo "<<< __vsa_config $*"
	fi >&2

	eval "${cfg_script}"
}


if [[ $# -eq 0 ]] || ! [ -r "${__vsa_bindir}/vserver-admin-$1" ]
then
	# No action give: print action list
	echo "Usage: vserver-admin action [action-arguments...]"
	echo
	echo "Available actions:"
	for action in "${__vsa_bindir}"/vserver-admin-* ; do
		action=${action##*/}
		description=$(sed -e '/^##-/p' -e d "${__vsa_bindir}/${action}")
		printf "   %-10s %s\n" "${action#vserver-admin-}" "${description##\#\#-}"
	done
	echo
	echo "Use 'vserver-admin help action' for more information on a specific command"
	exit 1
fi


# Get action, save it, and pop it from the command line argument list
__vsa_action=$1
shift


# Define common stuff needed for all action scripts
#
__vsa_vdir=$(readlink /etc/vservers/.defaults/vdirbase || echo '${vdir}')

# Mark some stuff as read-only -- staying safe does not harm.
declare -r __vsa_vdir


# Do some sanity checking
if [[ ${VSERVER_ADMIN_DEBUG} -eq 0 ]] && [[ ${__vsa_action} != help ]] && ! __vsa_sanity_check
then
	cat >&2 <<-EOF
		There is something bad about your vServer setup! Please check that:

		 - Your kernel supports vServer (no /proc/virtual?)
		 - ${__vsa_vdir} has permissions different than 700.
		 - You have the util-vserver package installed.
		 - The /etc/vservers directory exists.
		 - You use vserver-admin as root.

	EOF
	exit 42
fi


# Let it go!, go!!, go!!!
source "${__vsa_bindir}/vserver-admin-${__vsa_action}"


