Support a base cgroup.
[util-vserver.git] / scripts / vserver.functions
index e2f7507..1dc880a 100644 (file)
@@ -20,6 +20,7 @@
 #  $VSERVER_NAME  ... name of vserver
 
 declare -a NICE_CMD=()
+declare -a IONICE_CMD=()
 declare -a CHBIND_CMD=()
 declare -a CAP_OPTS=()
 declare -a CHCONTEXT_INIT_OPTS=()
@@ -40,12 +41,16 @@ declare -a ENTER_SHELL=()
 declare -a OPTS_VCONTEXT_CREATE=()
 declare -a OPTS_VCONTEXT_MIGRATE=()
 declare -a OPTS_VCONTEXT_ENTER=()
+OPT_VCONTEXT_CHROOT=--chroot
 declare -a OPTS_VATTRIBUTE=( --flag fakeinit )
 declare -a OPTS_VSCHED=()
 declare -a OPTS_ENV=()
 declare -a OPTS_VTAG_CREATE=()
 declare -a OPTS_VTAG_ENTER=()
 declare -a OPTS_VMEMCTRL=()
+declare -a OPTS_VSPACE=( --default )
+declare -a OPTS_VSPACE_SHARED=()
+declare -a VSPACE_SHARED_CMD=()
 
 declare -a STOPCMD_PREPARE=()
 
@@ -65,9 +70,15 @@ _IS_FAKEINIT=
 INITSTYLE=sysv
 
 S_CONTEXT=
+N_CONTEXT=
 
 SILENT_OPT=
 
+CGROUP_MNT=/dev/cgroup
+CGROUP_SUBSYS=all
+declare -a CGROUP_INHERIT=( cpuset.cpus cpuset.mems )
+CGROUP_BASE=""
+
 : ${VSERVER_NAME:=$(basename "$VSERVER_DIR")}
 
 if test -e "$VSERVER_DIR"/noisy -o -n "$OPTION_VERBOSE"; then
@@ -102,14 +113,18 @@ function _generateChbindOptions
 
     test -n "$_HAVE_INTERFACE_OPTIONS" || _generateInterfaceOptions "$vdir"
 
+    if test -e "$vdir"/noncontext -o \
+           \( -e "$vdir"/spaces/net -a ! -e "$vdir"/ncontext \); then
+       _HAVE_CHBIND_OPTIONS=1
+       return 0
+    fi
+
     local f="$vdir"/interfaces/bcast
     getFileValue bcast "$f"
     f="$vdir"/interfaces/lback
     getFileValue lback "$f"
 
-    getFileValue nid "$vdir/ncontext" "$vdir/context"
-    
-    CHBIND_CMD=( $_CHBIND $SILENT_OPT --secure ${nid:+--nid "$nid"}
+    CHBIND_CMD=( $_CHBIND $SILENT_OPT --secure ${N_CONTEXT:+--nid "$N_CONTEXT"}
                 ${bcast:+--bcast "$bcast"} ${lback:+--lback "$lback"}
                )
 
@@ -135,6 +150,19 @@ function _generateNiceCommand
     NICE_CMD=( $_NICE -n $nice )
 }
 
+function _generateIONiceCommand
+{
+    local vdir=$1
+    local ionice_class=2
+    local ionice_priority=0
+
+    test -n "$_IONICE" || return 0
+
+    test -r "$vdir/ionice/class" && read ionice_class <"$vdir"/ionice/class
+    test -r "$vdir/ionice/priority" && read ionice_priority <"$vdir"/ionice/priority
+
+    IONICE_CMD=( $_IONICE -c$ionice_class -n$ionice_priority )
+}
 
 function _generatePersonalityOptions
 {
@@ -315,12 +343,29 @@ function _generateInitOptions
 
        (xgentoo)
            test -n "$RUNLEVEL_START" || RUNLEVEL_START="default"
+           RC_PATH=/usr/sbin:/usr/bin:/sbin:/bin
+
+           if test -x "$vdir/vdir/lib/rcscripts/sh/init-vserver.sh"; then
+               RC_WRAP=/lib/rcscripts/sh/init-vserver.sh
+           elif test -x "$vdir/vdir/lib/rc/sh/init-vserver.sh"; then
+               RC_WRAP=/lib/rc/sh/init-vserver.sh
+           else
+               panic "init-vserver.sh not found; aborting"
+           fi
 
-           INITCMD_START=( env TERM=$TERM /lib/rcscripts/sh/init-vserver.sh "$RUNLEVEL_START" )
-           INITCMD_STOP=( env -i TERM=$TERM RUNLEVEL=0 /sbin/rc shutdown )
+           OPTS_ENV=( "${OPTS_ENV[@]}" TERM=$TERM )
+           INITCMD_START=( $RC_WRAP "$RUNLEVEL_START" )
+           INITCMD_STOP=( env -i PATH=$RC_PATH TERM=$TERM RUNLEVEL=0 /sbin/rc shutdown )
            INITCMD_PREPARE=( $_FAKE_RUNLEVEL 3 /var/run/utmp )
            ;;
 
+       (xarch)
+           test -n "$RUNLEVEL_START" || RUNLEVEL_START=3
+           INITCMD_START=( /etc/rc.multi )
+           INITCMD_STOP=( /etc/rc.shutdown )
+           INITCMD_PREPARE=( $_FAKE_RUNLEVEL "$RUNLEVEL_START" /var/run/utmp )
+           ;;
+
        (x) ;;
        (*) panic "Unknown init-style '$INITSTYLE'; aborting";;
     esac
@@ -349,10 +394,12 @@ function _generateInitOptions
 function _generateFlagOptions
 {
     local vdir=$1
+    local file
 
     CHCONTEXT_FLAG_OPTS=()
 
-    test ! -e "$vdir"/flags || \
+    findFile file "$vdir"/cflags "$vdir"/flags ""
+    test -z "$file" || \
     while read flag; do
        case x"$flag" in
            (x|x\#*)            ;;
@@ -366,11 +413,13 @@ function _generateFlagOptions
                                      --flag "$flag" )
                ;;
        esac
-    done <"$vdir"/flags
+    done <"$file"
 
     isAvoidNamespace "$vdir" || {
        USE_VNAMESPACE=1
        CHCONTEXT_FLAG_OPTS=( "${CHCONTEXT_FLAG_OPTS[@]}" --flag namespace )
+       ! $_VSERVER_INFO - FEATURE PIVOT_ROOT || \
+           OPT_VCONTEXT_CHROOT=--pivot-root
     }
 }
 
@@ -510,6 +559,33 @@ create them before starting the vserver and use the 'nodev' flag then"
     return 0
 }
 
+function _getTunInfo
+{
+    local iface="$1"
+
+    test -e "$iface/tun" -o -e "$iface/tap" || return 1
+    test ! -e "$iface/tun"     || echo --tun
+    test ! -e "$iface/tap"     || echo --tap
+    test ! -e "$iface/nocsum"  || echo --~checksum
+    test   -e "$iface/shared"  || echo --nid-failure-ok "$N_CONTEXT"
+    if test -e "$iface/uid"; then
+       local uid
+       getFileValue uid "$iface/uid"
+       echo --uid "$uid"
+    fi
+    if test -e "$iface/gid"; then
+       local gid
+       getFileValue gid "$iface/gid"
+       echo --gid "$gid"
+    fi
+    if test -e "$iface/linktype"; then
+       local linktype
+       getFileValue linktype "$iface/linktype"
+       echo --linktype "$linktype"
+    fi
+    return 0
+}
+
 ## Usage: _processSingleInterface <interface-directory>
 function _processSingleInterface
 {
@@ -525,6 +601,7 @@ function _processSingleInterface
     local mac
     local extip
     local up="up"
+    local peer
 
     _getInterfaceValue ip     '' "$iface"
     _getInterfaceValue extip  '' "$iface" "$iface/.."
@@ -535,6 +612,7 @@ function _processSingleInterface
     _getInterfaceValue name   '' "$iface"
     _getInterfaceValue scope  '' "$iface" "$iface/.."
     _getInterfaceValue mac    '' "$iface"
+    _getInterfaceValue peer   '' "$iface"
 
     test -n "$ip" || { echo $"Can not read ip for '$iface'"  >&2; return 1; }
     test -n "$dev" -o -e "$iface"/nodev || {
@@ -569,21 +647,27 @@ function _processSingleInterface
 
        if ! test -e "$iface"/indirect; then
            # XXX: IPv6 hack
-           use_bcast="broadcast ${bcast:-+}"
+           local use_bcast="broadcast ${bcast:-+}"
            echo "$ip" | $_GREP -q : && use_bcast=
-           _addInterfaceCmd IP_ADDR  "$ip${prefix:+/$prefix}" $use_bcast ${name:+label "$dev:$name"} dev "$dev"
+
+           local tun_info
+           if tun_info=$(_getTunInfo "$iface"); then
+               _addInterfaceCmd TUNCTL "$dev" $tun_info
+           fi
+
+           _addInterfaceCmd IP_ADDR  "$ip${prefix:+/$prefix}" $use_bcast ${name:+label "$dev:$name"} dev "$dev" ${peer:+peer "$peer"}
            #_addInterfaceCmd IP_ROUTE "$ip${prefix:+/$prefix}" dev "$dev"
            _addInterfaceCmd IP_LINK  "$dev" $up
-       elif ! test -n "$ctx"; then
+       elif ! test -n "$N_CONTEXT"; 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
+           test -z "$mac" || _generateMac mac "$(basename $iface)" "$N_CONTEXT" || 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"
+           test -z "$extip" || _addInterfaceCmd IPTABLES "$ip${prefix:+/$prefix}" ${name:+label "$dev:$name"} "$N_CONTEXT" "$extip"
        fi
 
        break
@@ -594,9 +678,11 @@ function _processSingleInterface
 function _generateInterfaceOptions
 {
     local iface
-    local ctx
 
-    test ! -e "$1"/context || read ctx <"$1"/context
+    # XXX: This is here instead of in _generateChbindOptions
+    #      to avoid a circular dependency
+    getFileValue N_CONTEXT "$1/ncontext" "$1/context"
+    test -n "$N_CONTEXT" -o -z "$S_CONTEXT" || N_CONTEXT="$S_CONTEXT"
 
     for iface in "$1/interfaces/"*; do
         test   -d "$iface"          || continue
@@ -635,6 +721,11 @@ function enableInterfaces
            IP_ADDR_FLUSH)      $_IP addr  flush "$@";;
            IP_LINK)            $_IP link  set   "$@";;
            IP_ROUTE)           $_IP route add   "$@";;
+           TUNCTL)
+               local dev="$1"
+               shift
+               $_TUNCTL --persist "$@" "$dev"
+               ;;
            *)                  echo "Unknown interface-command type '$type'" >&2; false;;
        esac
 
@@ -670,6 +761,7 @@ function disableInterfaces
            IP_ADDR_FLUSH)      ;;
            IP_LINK)            ;; ## Ignore the link-down command for now
            IP_ROUTE)           $_IP route del "$@";;
+           TUNCTL)             $_TUNCTL --~persist "$1";;
            *)                  echo "Unknown interface-command type '$type'" >&2; false;;
        esac
     done
@@ -691,7 +783,7 @@ function _generateTagOptions
 
 function _generateMemctrlOptions
 {
-    local vdir="$"
+    local vdir="$1"
     local badness
 
     getFileValue badness "$vdir/badness"
@@ -700,14 +792,40 @@ function _generateMemctrlOptions
     OPTS_VMEMCTRL=( --badness "$badness" )
 }
 
+function _generateSpaceOptions
+{
+    local vdir="$1"
+    local d="$vdir"/spaces
+    local shared
+    local space
+
+    for space in pid net; do
+       if test -e "$d"/$space; then
+           getFileValue shared "$d"/$space || shared=""
+           if test -z "$shared"; then
+               OPTS_VSPACE=( "${OPTS_VSPACE[@]}" --$space )
+           elif test "$shared" = "0"; then
+               : # Do nothing
+           else
+               OPTS_VSPACE_SHARED=( "${OPTS_VSPACE_SHARED[@]}" --$space )
+               VSPACE_SHARED_CMD=( "${VSPACE_SHARED_CMD[@]}" $_VSPACE --enter "$shared" --$space -- )
+           fi
+       fi
+    done
+
+    local mask
+    getFileValue mask "$d"/mask || \
+      OPTS_VSPACE=( "${OPTS_VSPACE[@]}" --mask "$mask" )
+}
+
 ## 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
+           { $_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 || :
@@ -739,12 +857,15 @@ function generateOptions
     _generateInterfaceOptions   "$1"
     test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1" 
     _generateNiceCommand        "$1"
+    _generateIONiceCommand     "$1"
     _generateInitOptions        "$1"
     _generateChcontextOptions   "$1"
     _generateScheduleOptions    "$1"
     _generatePersonalityOptions "$1"
     _generateTagOptions         "$1"
     _generateMemctrlOptions     "$1"
+    _generateSpaceOptions       "$1"
+    _generateCgroupOptions
 
     if test -n "$_IS_FAKEINIT"; then
        CHCONTEXT_INIT_OPTS=( --disconnect --flag fakeinit )
@@ -813,6 +934,10 @@ function _mountVserverInternal
     # 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
+    if test -h etc/mtab; then
+       local l=$($_READLINK etc/mtab)
+       test "${l##/proc/}" = "$l" || xflag=-n
+    fi
     "$@" $_SECURE_MOUNT -a $xflag --chroot --fstab "$fstab" --rootfs no
     popd >/dev/null
 }
@@ -840,6 +965,8 @@ function mountVserver
     local ns_opt=$2
     local vdir=$1/vdir
     local mtab_src
+    local extra_opt=
+    local real_vdir
 
     test -e "$cfgdir"/fstab -o \
          -e "$cfgdir"/fstab.local -o \
@@ -860,8 +987,11 @@ function mountVserver
     isNamespaceCleanup "$cfgdir" && \
         _namespaceCleanup "$cfgdir"
 
+    real_vdir=$(getPhysicalDir "$vdir")
     isAvoidNamespace "$cfgdir" || \
-       $_SECURE_MOUNT --rbind -n "$vdir" "/"
+       $_SECURE_MOUNT --rbind -n -o dev "$vdir" "$real_vdir"
+    ! $_VSERVER_INFO - FEATURE PIVOT_ROOT || \
+       $_SECURE_MOUNT -n -o shared,rec "$real_vdir" "$real_vdir"
 }
 
 function _umountVserverInternal
@@ -946,7 +1076,7 @@ function initWait
            rc=$?
 
            if test "$rc" -ne 0 -a "$rc" -ne 1; then
-               $_VPS axf | $_EGREP -e "^[^ \t]+[ \t]+$S_CONTEXT[ \t]+" >&4
+               $_VPS axf | $_EGREP -e "^[ \t]*[^ \t]+[ \t]+$S_CONTEXT[ \t]+" >&4
                killContext "$S_CONTEXT" 9
            fi
 
@@ -1103,7 +1233,7 @@ WARNING: There is no cachedirectory configured for this vserver;
   ln -s ../.defaults/cachebase/$VSERVER_NAME $cfgdir/cache
 "
 
-    find "$cfgdir" -type f -exec "$_CHECK_UNIXFILE" '{}' ';'
+    $_FIND "$cfgdir" -type f -exec "$_CHECK_UNIXFILE" '{}' ';'
 
     vshelper.doSanityCheck
 
@@ -1265,10 +1395,11 @@ function _namespaceCleanup
     done
 
     local -a list_umount
-    while read dev path opts; do
+    while read -r dev path opts; do
        test -n "$path" || continue
        for i in "$root" /dev /proc; do
-           test "${path#$i}" != "$path" && continue 2
+           path_dir="${path}/"
+           test "${path_dir#${i}/}" != "${path_dir}" && continue 2
        done
        for i in "${list[@]}" /; do
            test "$path" = "$i" && continue 2
@@ -1278,7 +1409,7 @@ function _namespaceCleanup
     done < /proc/mounts
     # separate loop to avoid races while reading /proc/mounts
     for i in "${list_umount[@]}"; do
-       $_UMOUNT -l -n "$i"
+       $_UMOUNT -l -n -i "$i"
     done
 }
 
@@ -1309,3 +1440,127 @@ function handleDeviceMap
        $_VDEVMAP --xid "$xid" "$op" "${vdevmap_opts[@]}" || return $?
     done
 }
+
+function hasCgroup
+{
+    $_GREP -q "cgroup" /proc/filesystems
+}
+
+function _generateCgroupOptions
+{
+    local file
+
+    hasCgroup || return 0
+
+    findFile file "$__CONFDIR/.defaults/cgroup/mnt" ""
+    if test -n "$file"; then
+       read CGROUP_MNT < "$file"
+    fi
+    findFile file "$__CONFDIR/.defaults/cgroup/subsys" ""
+    if test -n "$file"; then
+       read CGROUP_SUBSYS < "$file"
+    elif $_GREP -q '^ns[[:space:]]' /proc/cgroups; then
+       # Hack for the ns subsystem, with which we are incompatible
+       CGROUP_SUBSYS=$($_SED '/^#/d;/^ns[[:space:]]/d;s/[[:space:]].*//' /proc/cgroups | \
+                       (s=""; while read x; do test -n "$s" && s="$s,"; s="$s$x"; done; echo "$s"))
+    fi
+    findFile file "$__CONFDIR/.defaults/cgroup/inherit" ""
+    if test -n "$file"; then
+       _readFileToArray CGROUP_INHERIT "$file" ""
+    fi
+    findFile file "$__CONFDIR/.defaults/cgroup/base" ""
+    if test -n "$file"; then
+       read CGROUP_BASE < "$file"
+       test "$CGROUP_BASE" != "${CGROUP_BASE%/}" || \
+           CGROUP_BASE="${CGROUP_BASE}/"
+    fi
+
+    return 0
+}
+
+function useCgroup
+{
+    hasCgroup || return 1
+    test -d "$CGROUP_MNT" || return 1
+    memcg=""
+    if $_VSERVER_INFO - FEATURE memcg; then
+       memcg=1
+    fi
+    test -d "$1/cgroup" -o \
+       \( \( -d "$__CONFDIR/.defaults/cgroup" -o -n "$memcg" \) -a \
+          ! -e "$1/nocgroup" \)
+}
+
+function _handleCgroup
+{
+    local action="$1"
+    local vdir="$2"
+    local dir
+    local name
+    local i
+    local j
+    local parent
+
+    useCgroup "$vdir" || return 0
+
+    findDir dir "$vdir/cgroup" "$__CONFDIR/.defaults/cgroup" ""
+
+    if test -n "$dir" -a -r "$dir"/name; then
+       read name < "$dir"/name
+    else
+       name="$VSERVER_NAME"
+    fi
+
+    if test "$action" = "attach"; then
+       if test -n "$CGROUP_BASE"; then
+           local -a dirs=()
+           i="$CGROUP_MNT/$CGROUP_BASE"
+           while test "$CGROUP_MNT" != "$i"; do
+               dirs=( "$i" "${dirs[@]}" )
+               i="${i%/*}"
+           done
+           for i in "${dirs[@]}"; do
+               if mkdir "$i" 2>/dev/null; then
+                   parent="${i%/*}"
+                   for j in "${CGROUP_INHERIT[@]}"; do
+                       test -f "$parent/$j" || continue
+                       cat "$parent/$j" > "$i/$j"
+                   done
+               fi
+           done
+       fi
+       if mkdir "$CGROUP_MNT/$CGROUP_BASE$name" 2>/dev/null; then
+           parent="$CGROUP_MNT/$CGROUP_BASE$name"
+           parent="${parent%/*}"
+            for i in "${CGROUP_INHERIT[@]}"; do
+               test -f "$parent/$i" || continue
+               cat "$parent/$i" > "$CGROUP_MNT/$CGROUP_BASE$name/$i"
+           done
+
+           if test -n "$dir"; then
+               shopt -s nullglob
+               for i in "$dir"/*; do
+                   f="${i##*/}"
+                   test "$f" != mnt -a "$f" != subsys -a \
+                       "$f" != inherit -a "$f" != name || continue
+                   cat "$i" > "$CGROUP_MNT/$CGROUP_BASE$name/$f"
+               done
+           fi
+       fi
+       echo "$$" > "$CGROUP_MNT/$CGROUP_BASE$name/tasks"
+    elif test "$action" = "destroy"; then
+       rmdir "$CGROUP_MNT/$name" 2>/dev/null || :
+    fi
+
+    return 0
+}
+
+function attachToCgroup
+{
+    _handleCgroup attach "$@"
+}
+
+function destroyCgroup
+{
+    _handleCgroup destroy "$@"
+}