# $Id: vserver.functions,v 1.68 2006/01/22 12:24:25 ensc Exp $  --*- sh -*--

# Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
#  
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#  
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#  
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

## Expected env:
#  $VSERVER_DIR   ... path to vserver-cfg dir
#  $VSERVER_NAME  ... name of vserver

declare -a NICE_CMD=()
declare -a CHBIND_OPTS=()
declare -a CAP_OPTS=()
declare -a CHCONTEXT_INIT_OPTS=()
declare -a CHCONTEXT_FLAG_OPTS=()
declare -a CHCONTEXT_OPTS=()
declare -a CAPCHROOT_OPTS=()
declare -a INTERFACES=()

declare -a INITCMD_RESCUE=( /bin/sleep 900 )
declare -a INITCMD_START=()
declare -a INITCMD_START_SYNC=()
declare -a INITCMD_STOP=()
declare -a INITCMD_STOP_SYNC=()
declare -a INITCMD_PREPARE=()
declare -a INITKILL_SEQ=()
declare -a ENTER_SHELL=()

declare -a OPTS_VCONTEXT_CREATE=()
declare -a OPTS_VCONTEXT_MIGRATE=()
declare -a OPTS_VCONTEXT_ENTER=()
declare -a OPTS_VATTRIBUTE=( --flag fakeinit )
declare -a OPTS_VSCHED=()

declare -a STOPCMD_PREPARE=()

declare -a VSERVER_EXTRA_CMDS=()

INIT_RESCUE=
VSHELPER_SYNC_TIMEOUT=30
USE_VNAMESPACE=
INTERFACE_CMDS_IDX=0
RUNLEVEL_START=
RUNLEVEL_STOP=
_HAVE_INTERFACE_OPTIONS=
_HAVE_CHBIND_OPTIONS=
_NEED_VSHELPER_SYNC=
_IS_FAKEINIT=

INITSTYLE=sysv

S_CONTEXT=

SILENT_OPT=

: ${VSERVER_NAME:=$(basename "$VSERVER_DIR")}

if test -e "$VSERVER_DIR"/noisy -o -n "$OPTION_VERBOSE"; then
    SILENT_OPT=
else
    SILENT_OPT='--silent'
fi

function _generateChbindOptions
{
    local vdir="$1"
    local i
    local bcast=
    local nid=

    test -n "$_HAVE_INTERFACE_OPTIONS" || _generateInterfaceOptions "$vdir"

    local f=$vdir/interfaces/bcast
    getFileValue bcast "$f"

    getFileValue nid "$vdir/ncontext" "$vdir/context"
    
    CHBIND_OPTS=( $SILENT_OPT ${nid:+--nid "$nid"} ${bcast:+--bcast "$bcast"} )

    for i in "${INTERFACES[@]}"; do
	CHBIND_OPTS=( "${CHBIND_OPTS[@]}" --ip "$i" )
    done

    _HAVE_CHBIND_OPTIONS=1
}

function _generateNiceCommand
{
    local vdir=$1
    local nice

    test -r "$vdir/nice" || return 0;
    read nice <"$vdir"/nice

    NICE_CMD=( $_NICE -$nice )
}


function _generatePersonalityOptions
{
    local vdir="$1"
    local f="$vdir"/personality
    local type flags

    test -s "$f" || return 0

    {
	local delim tmp

	read type
	while read tmp; do
	    case x$tmp in
		(x\#*|x)	;;
		(*)		flags=$flags$delim$tmp
				delim=,
				;;
	    esac
	done
    } <"$f"

    OPTS_VCONTEXT_ENTER=( "${OPTS_VCONTEXT_ENTER[@]}"
                          --personality-type "$type"
			  ${flags:+--personality-flags "$flags"} )
}

function _generateCCapabilityOptions
{
    local vdir=$1
    local cap
    local f="$vdir"/ccapabilities
    
    test -e "$f" || return 0
    while read cap; do
	case x"$cap" in
	    (x|x\#*)	;;
	    (*)		OPTS_VATTRIBUTE=( "${OPTS_VATTRIBUTE[@]}" --ccap "$cap" );;
	esac
    done <"$f"
}

function _generateBCapabilityOptions
{
    local vdir=$1
    local cap
    local f="$vdir"/bcapabilities
    
    test -e "$f" || return 0
    while read cap; do
	case x"$cap" in
	    (x|x\#*)	;;
	    (*)		OPTS_VATTRIBUTE=( "${OPTS_VATTRIBUTE[@]}" --bcap "$cap" );;
	esac
    done <"$f"
}

function _generateCapabilityOptions
{
    local vdir=$1
    local cap

    _generateBCapabilityOptions "$vdir"
    _generateCCapabilityOptions "$vdir"
    
    test -e "$vdir"/capabilities || return 0

    CAP_OPTS=()
    CAPCHROOT_OPTS=()

    while read cap; do
	case x"$cap" in
	    (x|x\#*)	;;
	    (!CAP_SYSCHROOT)
		CAP_OPTS=( "${CAP_OPTS[@]}" --cap "$cap" )
		CAPCHROOT_OPTS=( "${CAPCHROOT_OPTS[@]}" --nochroot )
		;;
	    (*)
		CAP_OPTS=( "${CAP_OPTS[@]}" --cap "$cap" )
		;;
	esac
    done <"$vdir"/capabilities
}

function getEnterShell
{
    local vdir=$1
    local shell_file

    ENTER_SHELL=()

    getFileValue ENTER_SHELL "$vdir"/shell "$__CONFDIR"/.defaults/shell
    
    test -n "$ENTER_SHELL" || {
	local i
	for i in "/bin/bash -login" "/bin/sh -l" /bin/csh; do
	    set -- $i
	    test -x "$vdir/vdir/$1" || continue
	    ENTER_SHELL=( "$@" )
	    break
	done
    }
}

## Usage: sendKillSequence <ctx> <signal> [<wait> <signal>]*
function sendKillSequence
{
    local ctx=$1
    local wait=
    shift

    while isCtxRunning "$ctx"; do
	test -z "$wait" || sleep "$wait"

	killContext "$ctx" "$1"
	test -n "$2" || break
	wait="$2"
	shift 2
    done
}

function _generateInitOptions
{
    local vdir=$1
    local cfgdir=$vdir/apps/init
    local i f

    INITCMD_START=()
    INITCMD_STOP=()
    INITCMD_START_SYNC=()
    INITCMD_STOP_SYNC=()
    INITCMD_PREPARE=()
    STOPCMD_PREPARE=()

    INITKILL_SEQ=( 15 5 9 )
    CHCONTEXT_INIT_OPTS=()


    getFileValue INITSTYLE      "$cfgdir"/style
    getFileValue RUNLEVEL_START "$cfgdir"/runlevel
    getFileValue RUNLEVEL_START "$cfgdir"/runlevel.start
    getFileValue RUNLEVEL_STOP  "$cfgdir"/runlevel.stop
    getFileArray INITKILL_SEQ	"$cfgdir"/killseq || :

    case x"$INITSTYLE" in
	(xrescue)
	    INITCMD_START=( "${INITCMD_RESCUE[@]}" )
	    INITCMD_STOP=( /sbin/killall5 )
	    _IS_FAKEINIT=1
	    _NEED_VSHELPER_SYNC=
	    ;;
	    
	(xsysv)
	    test -n "$RUNLEVEL_START" || RUNLEVEL_START=3
	    test -n "$RUNLEVEL_STOP"  || RUNLEVEL_STOP=6

	    for i in /etc/init.d/rc /etc/rc.d/rc; do
		test -x "$vdir/vdir/$i" || continue
		INITCMD_START=( "$i" "$RUNLEVEL_START" )
		INITCMD_STOP=(  "$i" "$RUNLEVEL_STOP"  )
	    done
	    INITCMD_PREPARE=( $_FAKE_RUNLEVEL "$RUNLEVEL_START" /var/run/utmp )
	    ;;
	    
	(xplain)
	    INITCMD_START=( /sbin/init )
	    INITCMD_STOP=(  /sbin/init )
	    _IS_FAKEINIT=1
	    _NEED_VSHELPER_SYNC=1
	    test -z "$RUNLEVEL_START" || INITCMD_START=( "${INITCMD_START[@]}" "$RUNLEVEL_START" )
	    test -z "$RUNLEVEL_STOP"  || INITCMD_STOP=(  "${INITCMD_STOP[@]}"  "$RUNLEVEL_STOP"  )
	    ;;
	    
	(xminit)
	    INITCMD_START=( /sbin/minit-start )
	    INITCMD_STOP=(  /sbin/minit-stop  )
	    _IS_FAKEINIT=1
	    INITCMD_START_SYNC=( "$_INITSYNC_MINIT_START" "$vdir" )
	    _NEED_VSHELPER_SYNC=1
	    test -z "$RUNLEVEL_START"         || INITCMD_START=( "${INITCMD_START[@]}" "$RUNLEVEL_START" )
	    test -z "$RUNLEVEL_STOP"          || INITCMD_STOP=(  "${INITCMD_STOP[@]}"  "$RUNLEVEL_STOP"  )
	    ! isNumber "${RUNLEVEL_START:-3}" || INITCMD_PREPARE=( $_FAKE_RUNLEVEL "${RUNLEVEL_START:-3}" /var/run/utmp )
	    ;;

	(xgentoo)
	    INITCMD_START=( /sbin/rc default  )
	    INITCMD_STOP=(  /sbin/rc shutdown )
	    ;;

	(x) ;;
	(*) panic "Unknown init-style '$INITSTYLE'; aborting";;
    esac

    if test x"$INITSTYLE" != xrescue; then
	getFileArray INITCMD_START      "$cfgdir"/cmd.start      || :
	getFileArray INITCMD_STOP       "$cfgdir"/cmd.stop       || :
	getFileArray INITCMD_START_SYNC "$cfgdir"/cmd.start-sync || :
	getFileArray INITCMD_STOP_SYNC  "$cfgdir"/cmd.stop-sync  || :
	getFileArray INITCMD_PREPARE    "$cfgdir"/cmd.prepare    || :
    fi
    
    test -n "$OPTION_FORCE_SYNC" -o -e "$cfgdir"/sync || {
	INITCMD_START_SYNC=()
	INITCMD_STOP_SYNC=()
	_NEED_VSHELPER_SYNC=
    }

    if vshelper.isEnabled; then
	vshelper.getSyncTimeout "$vdir" VSHELPER_SYNC_TIMEOUT || :
    else
	_NEED_VSHELPER_SYNC=
    fi
}

function _generateFlagOptions
{
    local vdir=$1

    CHCONTEXT_FLAG_OPTS=()

    test ! -e "$vdir"/flags || \
    while read flag; do
	case x"$flag" in
	    (x|x\#*)		;;
	    (xnamespace)	;;
	    (xfakeinit)
		_IS_FAKEINIT=1
		;;
	    (*)
		OPTS_VATTRIBUTE=( "${OPTS_VATTRIBUTE[@]}" --flag "$flag" )
	    	CHCONTEXT_FLAG_OPTS=( "${CHCONTEXT_FLAG_OPTS[@]}"
				      --flag "$flag" )
		;;
	esac
    done <"$vdir"/flags

    isAvoidNamespace "$vdir" || {
	USE_VNAMESPACE=1
	CHCONTEXT_FLAG_OPTS=( "${CHCONTEXT_FLAG_OPTS[@]}" --flag namespace )
    }
}

function _generateChcontextOptions
{
    local vdir=$1
    local ctx hostname domainname
    local cap_opts
    local flag

    {
	read ctx        <"$vdir"/context        || :
	## LEGACY ALERT
	read hostname   <"$vdir"/uts/nodename   || read hostname   <"$vdir"/hostname   || :
	read domainname <"$vdir"/uts/domainname || read domainname <"$vdir"/domainname || :
    } 2>/dev/null

    test -z "$S_CONTEXT" || ctx=$S_CONTEXT

    _generateCapabilityOptions "$vdir"
    _generateFlagOptions       "$vdir"

    CHCONTEXT_OPTS=( $SILENT_OPT \
                     "${CHCONTEXT_FLAG_OPTS[@]}" \
		     "${CAP_OPTS[@]}" \
		     --secure
		     ${ctx:+--ctx "$ctx"} \
                     ${hostname:+--hostname "$hostname"} \
		     ${domainname:+--domainname "$domainname"} )

    OPTS_VCONTEXT_CREATE=( $SILENT_OPT \
			   ${ctx:+--xid "$ctx"} )
    ## put '--secure' at front so that it can be overridden
    OPTS_VATTRIBUTE=( --secure --flag default "${OPTS_VATTRIBUTE[@]}" )
}

function _generateScheduleOptions
{
    local vdir=$1
    local f="$vdir"/schedule
    test -e "$f" || return 0

    local fill_rate interval tokens tokens_min tokens_max prio_bias
    {
	{
	    read fill_rate   && \
	    read interval    && \
	    read tokens      && \
	    read tokens_min  && \
	    read tokens_max  && \
	    read prio_bias   || prio_bias=
	} <"$f"
    } 2>/dev/null

    test -n "$prio_bias" || {
	echo $"Bad content in '$f'; aborting..." >&2
	false
    }

    OPTS_VSCHED=( --fill-rate  "$fill_rate"  --interval "$interval" \
		  --tokens     "$tokens"     --tokens_min "$tokens_min" \
		  --tokens_max "$tokens_max" --priority-bias "$prio_bias" )
}

function _getInterfaceValue
{
    local _giv_val=$1
    local _giv_dflt=$2
    shift 2
    
    local _giv_i
    local _giv_tmp

    for _giv_i; do
	read _giv_tmp  <"$_giv_i/$_giv_val" && break || :
    done 2>/dev/null

    : ${_giv_tmp:=$_giv_dflt}
    eval $_giv_val=\$_giv_tmp
}

## Usage: _transformMask2Prefix <result-varname> <prefix> <mask>
function _transformMask2Prefix
{
    local _tm2p_tmp=$2
    
    test -n "$_tm2p_tmp" || {
	$_MASK2PREFIX "$3" || _tm2p_tmp=$?
    }

    eval $1=\$_tm2p_tmp
    return 0
}

function _addInterfaceCmd
{
    eval INTERFACE_CMDS_${INTERFACE_CMDS_IDX}='( "$@" )'
    let ++INTERFACE_CMDS_IDX
}

## Usage: _generateMac <var> <iface> <ctx>
function _generateMac
{
    isNumber "$2" || {
	echo $"Interface basename '$iface' must be either a number, or the mac must be configured explicitly" >&2
	return 1
    }

    eval $1=$(printf "f0:ff:%02x:%02x:%02x:%02x" $[ (~($2>>8)) & 0xff ] $[ ($2 & 0xff) ] $[ ($3>>8) & 0xff ] $[ $3 & 0xff ])
}

function _getVLANInfo
{
    case "$1" in
	(vlan????)
	    panic "\
creation of VLAN_PLUS_VID devices is not supported; please create them
before starting the vserver and use the 'nodev' flag then"
	    echo "$1 vlan ${1##vlan} VLAN_PLUS_VID"
	    ;;
	(vlan*)
	    panic "\
creation of VLAN_PLUS_VID_NO_PAD devices is not supported; please
create them before starting the vserver and use the 'nodev' flag then"
	    echo "$1 vlan ${1##vlan} VLAN_PLUS_VID_N0_PAD"
	    ;;
	(*.????)	echo "$1 ${1%%.*} ${1##*.} DEV_PLUS_VID";;
	(*.*)		echo "$1 ${1%%.*} ${1##*.} DEV_PLUS_VID_NO_PAD";;
	(*)		return 1
    esac

    return 0
}

## Usage: _processSingleInterface <interface-directory>
function _processSingleInterface
{
    local iface=$1

    local ip
    local dev
    local prefix
    local mask
    local bcast
    local name
    local scope
    local mac
    local extip
    local up="up"

    _getInterfaceValue ip     '' "$iface"
    _getInterfaceValue extip  '' "$iface" "$iface/.."
    _getInterfaceValue dev    '' "$iface" "$iface/.."
    _getInterfaceValue prefix '' "$iface" "$iface/.."
    _getInterfaceValue mask   '' "$iface" "$iface/.."
    _getInterfaceValue bcast  '' "$iface" "$iface/.."
    _getInterfaceValue name   '' "$iface"
    _getInterfaceValue scope  '' "$iface" "$iface/.."
    _getInterfaceValue mac    '' "$iface"

    test -n "$ip" || { echo $"Can not read ip for '$iface'"  >&2; return 1; }
    test -n "$dev" -o -e "$iface"/nodev || {
	echo $"No device specified for '$iface'" >&2
	return 1;
    }

    test ! -e "$iface"/down || up=

    while true; do
	_transformMask2Prefix prefix "$prefix" "$mask"
	INTERFACES=( "${INTERFACES[@]}" "$ip${prefix:+/$prefix}" )

	test ! -e "$iface"/nodev   || break
	## LEGACY ALERT
	test ! -e "$iface"/only_ip || break

	local vlan_info
	if vlan_info=$(_getVLANInfo "$dev"); then
	    test -d /proc/net/vlan || {
		echo -e $"VLAN device-name used, but vlan subsystem not enabled.\nTry to execute 'modprobe 8021q' before starting the vservers"  >&2
		return 1
	    }
	    test -f /proc/net/vlan -o -e "$iface"/novlandev || {
		_addInterfaceCmd VCONFIG $vlan_info
	    }
	fi

	if ! test -e "$iface"/indirect; then
	    _addInterfaceCmd IP_ADDR  "$ip${prefix:+/$prefix}" broadcast ${bcast:-+} ${name:+label "$dev:$name"} dev "$dev"
	    #_addInterfaceCmd IP_ROUTE "$ip${prefix:+/$prefix}" dev "$dev"
	    _addInterfaceCmd IP_LINK  "$dev" $up
	elif ! test -n "$ctx"; then
	    echo $"Using 'dummy' (indirect) for interface '$dev' requires a fixed context number; dynamic ctx are not supported" >&2
	    return 1
	else
	    test -z "$mac" || _generateMac mac "$(basename $iface)" "$ctx" || return 1
	    _addInterfaceCmd MODPROBE dummy "$dev"
	    _addInterfaceCmd IP_LINK  dev dummy0 address "$mac"
	    _addInterfaceCmd NAMEIF   "$dev" "$mac"
	    _addInterfaceCmd IP_ADDR  "$ip${prefix:+/$prefix}" dev "$dev"
	    test -z "$extip" || _addInterfaceCmd IPTABLES "$ip${prefix:+/$prefix}" ${name:+label "$dev:$name"} "$ctx" "$extip"
	fi

	break
    done
}

## Usage: _generateInterfaceOptions <vserver-directory>
function _generateInterfaceOptions
{
    local iface
    local ctx

    test ! -e "$1"/context || read ctx <"$1"/context

    for iface in "$1/interfaces/"*; do
        test   -d "$iface"          || continue
        test ! -e "$iface"/disabled || continue
    
        _processSingleInterface "$iface"
    done
    _HAVE_INTERFACE_OPTIONS=1
}

function enableInterfaces
{
    local i=0
    declare -a var

    lock "$__LOCKDIR"/vserver.interfaces

    while test $i -lt $INTERFACE_CMDS_IDX; do
	eval var='( "${INTERFACE_CMDS_'$i'[@]}" )'
	local type=${var[0]}
	unset var[0]

	set -- "${var[@]}"
	case "$type" in
	    IPTABLES)	;; ## TODO
	    MODPROBE)
		local mod=$1
		local name=$2
		shift 2
		$_MODPROBE ${name:+-o "$name"} "$mod" "$@"
		;;
	    NAMEIF)		$_NAMEIF   "$@";;
	    VCONFIG)		$_VCONFIG  set_name_type "$4"      >/dev/null
				$_VCONFIG  add           "$2" "$3" >/dev/null;;
	    IP_ADDR)		$_IP addr  add   "$@";;
	    IP_ADDR_FLUSH)	$_IP addr  flush "$@";;
	    IP_LINK)		$_IP link  set   "$@";;
	    IP_ROUTE)		$_IP route add   "$@";;
	    *)			echo "Unknown interface-command type '$type'" >&2; false;;
	esac

	let ++i
    done

    unlock 1
}

function disableInterfaces
{
    test -n "$_HAVE_INTERFACE_OPTIONS" || _generateInterfaceOptions "$1"

    local i=$INTERFACE_CMDS_IDX
    declare -a var

    lock "$__LOCKDIR"/vserver.interfaces
    
    while test $i -gt 0; do
	let --i || :

	eval var='( "${INTERFACE_CMDS_'$i'[@]}" )'
	local type=${var[0]}
	unset var[0]
	
	set -- "${var[@]}"
	case "$type" in
	    IPTABLES)		;; ## TODO
	    MODPROBE)		$_RMMOD "${2:-$1}";;
	    NAMEIF)		;;
	    VCONFIG)		$_VCONFIG  rem "$2.$3" >/dev/null;;
	    IP_ADDR)		$_IP addr  del "$@";;
	    IP_ADDR_FLUSH)	;;
	    IP_LINK)		;; ## Ignore the link-down command for now
	    IP_ROUTE)		$_IP route del "$@";;
	    *)			echo "Unknown interface-command type '$type'" >&2; false;;
	esac
    done

    unlock 1
}

## Usage: prepareInit <vserver-directory>
function prepareInit
{
    pushd "$1/vdir" >/dev/null
    case "$INITSTYLE" in
	sysv)
	    { find var/run  ! -type d -print0; \
	      find var/lock ! -type d -print0; } | xargs -0r $_CHROOT_SH rm
	    ;;
	plain)
	    $_CHROOT_SH rm .autofsck forcefsck 2>/dev/null || :
	    : | $_CHROOT_SH truncate fastboot  2>/dev/null || :
	    ;;
	minit)
	    ;;
    esac
    "${INITCMD_PREPARE[@]}"
    popd >/dev/null
}

## Usage: prepareInit <vserver-directory>
function prepareStop
{
    pushd "$1/vdir" >/dev/null
    case "$INITSTYLE" in
	(sysv)
	    export PREVLEVEL=$RUNLEVEL_START # required by Debian's initscripts
	    ;;
    esac
    "${STOPCMD_PREPARE[@]}"
    popd >/dev/null
}


function generateOptions
{
    _generateInterfaceOptions   "$1"
    test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1" 
    _generateNiceCommand        "$1"
    _generateInitOptions        "$1"
    _generateChcontextOptions   "$1"
    _generateScheduleOptions    "$1"
    _generatePersonalityOptions "$1"

    if test -n "$_IS_FAKEINIT"; then
	CHCONTEXT_INIT_OPTS=( --disconnect --flag fakeinit )
	OPTS_VCONTEXT_MIGRATE=( "${OPTS_VCONTEXT_MIGRATE[@]}" --initpid --disconnect )
    fi
}

function _mountVserverInternal
{
    local fstab="$1"
    local xflag=
    
    test -e "$fstab" || return 0
    shift

    pushd "$vdir" >/dev/null
    # check whether / is mounted readonly or whether there is special
    # magic regarding the mtab file; when etc/mtab can not be touched,
    # add the '-n' flag to mount
    test -w etc -o -w etc/mtab || xflag=-n
    "$@" $_SECURE_MOUNT -a $xflag --chroot --fstab "$fstab" --rootfs no
    popd >/dev/null
}

function mountRootFS
{
    local cfgdir=$1
    local vdir=$1/vdir
    local fstab="$cfgdir"/fstab
    local xflag=

    test -e "$fstab" || return 0
    pushd "$vdir" >/dev/null
    # check whether / is mounted readonly or whether there is special
    # magic regarding the mtab file; when etc/mtab can not be touched,
    # add the '-n' flag to mount
    test -w etc -o -w etc/mtab || xflag=-n
    $_SECURE_MOUNT -a $xflag --chroot --fstab "$fstab" --rootfs only -n
    popd >/dev/null
}

function mountVserver
{
    local cfgdir=$1
    local ns_opt=$2
    local vdir=$1/vdir
    local mtab_src

    test -e "$cfgdir"/fstab -o \
         -e "$cfgdir"/fstab.local -o \
	 -e "$cfgdir"/fstab.remote || return 0

    findObject -r mtab_src "$cfgdir"/apps/init/mtab "$__CONFDIR"/.defaults/init/mtab "$__PKGLIBDEFAULTDIR"/mtab /dev/null
    
    pushd "$vdir" >/dev/null
    $_CHROOT_SH truncate /etc/mtab <"$mtab_src"
    popd >/dev/null

    test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$cfgdir"

    test -z "$NAMESPACE_CLEANUP" || isAvoidNamespace "$cfgdir" || \
	$_VNAMESPACE --cleanup

    _mountVserverInternal "$cfgdir"/fstab
    _mountVserverInternal "$cfgdir"/fstab.local
    _mountVserverInternal "$cfgdir"/fstab.remote $_CHBIND "${CHBIND_OPTS[@]}"

    isAvoidNamespace "$cfgdir" || \
	$_SECURE_MOUNT --rbind -n "$vdir" "/"
}

function _umountVserverInternal
{
    local fstab="$1"
    test -e "$fstab" || return 0
    shift

    $_TAC "$fstab" | {
	is_ok=1
	while read src dst tmp; do
	    test -n "$tmp" || continue
	    case x"$src" in
		(x\#*)	continue;;
	    esac

	
	    "$@" $_EXEC_CD "$dst" $_UMOUNT -lfn . || is_ok=
	done
	test -n "$is_ok"
    }
}

function umountVserver
{
    local cfgdir=$1
    local vdir=$1/vdir
    local is_ok=1

    isAvoidNamespace "$cfgdir"    || return 0
    test -e "$cfgdir"/fstab -o \
         -e "$cfgdir"/fstab.local || return 0
    test -n "$_HAVE_CHBIND_OPTIONS"  || _generateChbindOptions "$cfgdir"
    
    pushd "$vdir/" >/dev/null || return 1
	_umountVserverInternal  "$cfgdir"/fstab.local                              || is_ok=
	_umountVserverInternal  "$cfgdir"/fstab       $_CHBIND "${CHBIND_OPTS[@]}" || is_ok=
    popd >/dev/null           || return 1

    test -n "$is_ok"
}

## Usage: waitForSync <vserver> <context> <vshelper-fifo-varname>
function initSync
{
    local _is_meth=sync
    test -n "$_NEED_VSHELPER_SYNC" && \
	! $_VSERVER_INFO - FEATURE vwait || _is_meth=async

    vshelper.initSync "$1" "$3" "$_is_meth"
}

## Usage: initWait <vserver> <context> <vwait-tmpdir-varname>
function initWait
{
    if $_VSERVER_INFO - FEATURE vwait; then
	local _is_tmpdir
	_is_tmpdir=$($_MKTEMPDIR vwaitstat.XXXXXX)

	(
	    $_VWAIT --timeout "$VSHELPER_SYNC_TIMEOUT" \
		--status-fd 3 "$2" \
		>>$_is_tmpdir/out 2>$_is_tmpdir/err 3>$_is_tmpdir/fifo
	    rc=$?

	    if test "$rc" -ne 0 -a "$rc" -ne 1; then
		$_VPS axf | $_EGREP -e "^[^ \t]+[ \t]+$S_CONTEXT[ \t]+" >&4
		killContext "$S_CONTEXT" 9
	    fi

	    exit $rc
	) 4>$_is_tmpdir/procs &
	    
	echo "$!" >$_is_tmpdir/pid
	eval "$3"=$_is_tmpdir
    fi </dev/null
}


## Usage: _waitForVWait <vserver> <fifo> <pid> <procs>
function _waitForVWait
{
    wait "$3" || :

    declare -a status
    declare -r procs=$(cat $4)

    getFileArray status "$2"
    set -- ${status[0]}

    case "$1" in
	(ERROR)		warning $"\
'vwait' exited with error '$2' which indicates that vserver could not
be stopped properly"
			;;
	(FINISHED)	;;
	(KILLED)	warning $"\
A timeout occured while waiting for the vserver to finish and it was
killed by sending a SIGKILL signal. Please investigate the reasons
and/or increase the timeout in apps/vshelper/sync-timeout."
			;;

	(TIMEOUT)	warning $"\
A timeout occured while waiting for the vserver to finish and it will
be killed by sending a SIGKILL signal. The following process list
might be useful for finding out the reason of this behavior:

----------------------------------------------------------------------
${procs:+$procs
}----------------------------------------------------------------------"
			;;

	(\?\?\?|*)	warning $"\
internal error: 'vwait' exited with an unexpected status '$1'; I will
try to continue but be prepared for unexpected events."
		    ;;
    esac

    return 0
}

## Usage: waitForSync <vserver> [<vshelper-fifo>] [<vwait-statdir>]
function waitForSync
{
    local cfgdir=$1
    local fifo=$2
    local vwait_statdir=$3
    local vwait_pid=$4

    if test -d "$vwait_statdir"; then
	_waitForVWait "$cfgdir" "$vwait_statdir/fifo" "$( <$vwait_statdir/pid )" "$vwait_statdir/procs"
    elif test -n "$_NEED_VSHELPER_SYNC"; then
	$_VSHELPER_SYNC "$fifo" "$VSHELPER_SYNC_TIMEOUT" || \
	    warning $"\
A timeout or other error occured while waiting for the synchronization
signal from vserver '$VSERVER_NAME'.
The vserver will be killed nevertheless..."
    elif test "${#INITCMD_STOP_SYNC[@]}" -ne 0; then
	"${INITCMD_STOP_SYNC[@]}" || \
	    warning $"\
Stop-synchronization for vserver '$VSERVER_NAME' failed. The vserver
will be killed nevertheless..."
    fi

    test -z "$OPTION_FORCE_SYNC" -a ! -e "$cfgdir"/sync ||
	sleep 1
}

function _sourceWrap
{
    local vdir name flavor start i already_handled base
    . "$@"
}

## Usage: execScriptlets <vserver-cfgdir> <vserver-name> <script-flavor>
function execScriptlets
{
    declare -r vdir=$1
    declare -r name=$2
    declare -r flavor=$3
    local base i

    for base in "$vdir"/scripts "$__CONFDIR"/.defaults/scripts; do
	local	DONT_SKIP_DEFAULTS=
	local	already_handled=
	
	for i in "$base/$flavor" "$base/$flavor.d"/*; do
	    isRegularFile "$i" || continue
	    test  -r "$i"      || continue

	    already_handled=1
	    local start=
	    test -x "$i" || start=_sourceWrap
	    $start "$i" "$flavor" "$name"
	done

	test -z "$already_handled" -o -n "$DONT_SKIP_DEFAULTS" || break
    done
}


function sanityCheck
{
    declare -r cfgdir=$1

    ! test -e "$cfgdir"/fstab.local ||
	warning $"\
WARNING: 'fstab' will *not* be executed in the network context of the
  vserver anymore. Therefore, 'fstab.local' has the same functionality
  and is obsoleted. When you need the old behaviour, put the mounts
  into 'fstab.remote'"

    ! test -e "$cfgdir"/hostname -a ! -L "$cfgdir"/hostname ||
	warning $"\
WARNING: The hostname is now configured in 'uts/nodename' but not in
  'hostname'."

    ! test -e "$cfgdir"/domainname -a ! -L "$cfgdir"/domainname ||
	warning $"\
WARNING: The domainname is now configured in 'uts/domainname' but not
  in 'domainname'." >&2

  
    local i
    for i in "$cfgdir"/interfaces/*/only_ip; do
	if test -e "$i"; then
	    local iface
	    iface=${i##$cfgdir/interfaces/}
	    iface=${iface%%/only_ip}
	    warning $"\
WARNING: The 'only_ip' flag for interface '$iface' is deprecated; use
  'nodev' instead of"
	fi
    done

    test ! -d "$cfgdir"/dlimits -o -L "$cfgdir/cache" || \
	warning $"\
WARNING: There is no cachedirectory configured for this vserver;
  please create '$cfgdir/cache' e.g. by executing

  ln -s ../.defaults/cachebase/$VSERVER_NAME $cfgdir/cache
"

    find "$cfgdir" -type f -exec "$_CHECK_UNIXFILE" '{}' ';'

    vshelper.doSanityCheck

    $_VSERVER_INFO - VERIFYCAP ||
	panic $"capabilities are not enabled in kernel-setup"

    $_VSERVER_INFO - VERIFYPROC ||
	panic $"\
/proc/uptime can not be accessed. Usually, this is caused by
procfs-security. Please read the FAQ for more details
http://www.linux-vserver.org/index.php?page=Linux-Vserver+FAQ"
}


function _setSingleDiskLimit
{
    local vdir=$1
    local dlimit=$2
    local space_used=
    local space_total=
    local inodes_used=
    local inodes_total=
    local reserved=
    local directory=
    local ctx=

    getFileValue ctx          "$vdir/context"
    getFileValue directory    "$dlimit/directory"    || return 0
    getFileValue space_total  "$dlimit/space_total"  || return 0
    getFileValue inodes_total "$dlimit/inodes_total" || return 0
    getFileValue reserved     "$dlimit/reserved"     || return 0

    local cachename=$ctx$directory
    cachename=dlimits/${cachename//\//_}

    test -e "$vdir/cache/$cachename" && . "$vdir/cache/$cachename"
    # Remove the cache so if the machine goes down unexpectedly, we won't have a stale cache
    $_RM -f "$vdir/cache/$cachename"

    if test -z "$inodes_used" -o -z "$space_used"; then
	local tmpvdu
	tmpvdu=`$_VDU --xid $ctx --space --inodes --script "$directory"`
	inodes_used=${tmpvdu##* }
	space_used=${tmpvdu%% *}
    fi

    $_VDLIMIT --xid $ctx \
	--set space_used=$space_used \
	--set space_total=$space_total \
	--set inodes_used=$inodes_used \
	--set inodes_total=$inodes_total \
	--set reserved=$reserved \
	"$directory"
}


function setDiskLimits
{
    local vdir=$1
    local dlimit

    # Disk Limits without a static context are useless
    test -e "$vdir"/context || return 0

    for dlimit in "$vdir/dlimits/"*; do
	test   -d "$dlimit"          || continue
	test ! -e "$dlimit/disabled" || continue

	_setSingleDiskLimit "$vdir" "$dlimit"
    done
}


function _saveSingleDiskLimit
{
    local vdir=$1
    local dlimit=$2
    local ctx=
    local directory=

    getFileValue ctx       "$vdir/context"
    getFileValue directory "$dlimit/directory" || return 0

    local cachename=$ctx$directory
    cachename=${cachename//\//_}

    # Things are getting ugly here... LFS says that /var/cache (where
    # cachename is usually pointing to) can vanish and applications
    # have to deal with it. So, we have to interprete the $vdir/cache
    # symlink and have to create the needed directories manually.
    if   test -d "$vdir/cache"; then
	:	# ok, exists already
    elif test -L "$vdir/cache"; then
	# it's a dangling symlink
	local link
	link=$($_READLINK "$vdir/cache")
	( cd $vdir && $_MKDIR -p "$link" )
    else
	return 0
    fi

    test -d "$vdir/cache"
    $_MKDIR -p "$vdir"/cache/dlimits

    $_VDLIMIT --xid $ctx "$directory" | \
	$_GREP '_used=' > "$vdir/cache/dlimits/$cachename"
}


function saveDiskLimits
{
    local vdir=$1
    local dlimit

    test -e "$vdir"/context || return 0

    for dlimit in "$vdir/dlimits/"*; do
	test   -d "$dlimit"          || continue
	test ! -e "$dlimit/disabled" || continue

	_saveSingleDiskLimit "$vdir" "$dlimit"
    done
}
