#!/bin/bash -ef

po_domain="alterator-mkve"

alterator_api_version=1
. alterator-sh-functions
. alterator-net-functions

# functions {{{

# veid {{{

# taken from /usr/bin/hsh-sh-functions
check_number()
{
    [ -n "${1##0*}" -a -n "${1##*[!0-9]*}" ] &&
        [ "$1" -gt 0 ] 2>/dev/null ||
        return 1
}

# return container id
veid()
{
    local veid="$(vzlist -a -H -oveid -N "$machine" 2>/dev/null | tr -d '[:space:]')"
    check_number "$veid" && printf %s "$veid"
}

# }}}
# read_param {{{
read_param()
{
    ( . /etc/vz/conf/$(veid).conf; eval echo "\$$1" )
}
# }}}
# write_param {{{
write_param()
{
    [ -n "$(read_param "$1")" ] &&
        sed -i "s/^$1=.*$/$1=\"$2\"/" /etc/vz/conf/$(veid).conf ||
        echo "$1=\"$2\"" >> /etc/vz/conf/$(veid).conf

}
# }}}

# }}}
# general {{{
write_add_ip_address()
{
    local err=

    if [ -n "$in_newip" ]; then
        err=$(vzctl set $(veid) --ipadd "$in_newip" --save 2>&1) ||
        echo "$err"
    fi

}

write_del_ip_address()
{
    local ip

    for ip in $(echo "$in_del_ip_list" | tr ';' ' '); do
        if ! err=$(vzctl set $(veid) --ipdel "$ip" --save 2>&1); then
            echo "$err"
            return
        fi
    done
}

write_password()
{
    local err

    err=$(vzctl set $(veid) --userpasswd root:"$in_password" --save 2>&1) ||
        echo "$err"
}

write_general()
{
    local err=

    if [ -n "$in_password" ]; then err=$(write_password)
    elif [ -n "$in_add_ip" ]; then err=$(write_add_ip_address)
    elif [ -n "$in_del_ip" ]; then err=$(write_del_ip_address)
    else
        err="why am i here?"
    fi

    [ -n "$err" ] && write_error "$err"
}

list_general_options()
{
    write_enum_item 'hostname' 'Hostname'
    write_enum_item 'ip_address' 'IP address'
    write_enum_item 'searchdomain' 'Search Domains'
    write_enum_item 'nameserver' 'Domain Name Servers (DNS)'
}

list_openvz_param()
{
    local p all=$(read_param "$1")

    while [ -n "$all" ]; do
        read p all <<< "$all"
        write_enum_item "$p"
    done
}

iplist()
{
    ip address show "$1" |
    sed --quiet -e 's/\s\+inet \([0-9]\+.[0-9]\+.[0-9]\+.[0-9]\+\/[0-9]\+\).*/\1/p'
}

list_interfaces()
{
    # local iface blacklist="(venet0|vnet0|mkvebr|virbr)"
    local iface blacklist="(venet0|vnet0)"

    list_iface |
    egrep -v "$blacklist" |
    while read iface; do
        write_table_item 'if' "$iface" 'ip' "$(iplist "$iface")"
    done
}
# }}}
# ubc {{{

fake_proc_bc_resources()
{
    (
        . /etc/vz/conf/$(veid).conf
        for name in             \
            NUMPROC             \
            NUMTCPSOCK          \
            NUMOTHERSOCK        \
            VMGUARPAGES         \
            KMEMSIZE            \
            TCPSNDBUF           \
            TCPRCVBUF           \
            OTHERSOCKBUF        \
            DGRAMRCVBUF         \
            OOMGUARPAGES        \
            LOCKEDPAGES         \
            PRIVVMPAGES         \
            SHMPAGES            \
            NUMFILE             \
            NUMFLOCK            \
            NUMPTY              \
            NUMSIGINFO          \
            DCACHESIZE          \
            NUMIPTENT           \
            PHYSPAGES           \
            #
        do
            echo $(echo $name | sed 's/\(.*\)/\L\1/') $(eval "echo \$$name" | sed 's/:/ /')
        done
    )
}

# get(name, field)
_ubc_get_generic()
{
    local file="$1"
    local name="$(echo "$2" | sed 's/\(.*\)/\L\1/')"
    local ret=$(fgrep "$name" "$file" 2>/dev/null | sed 's/ \+/ /g' | cut -f"$3" -d ' ')

    if [ -n "$ret" ]; then
       printf '%s' "$ret"
    else
       printf '%s' "0"
    fi
}

ubc_get_generic()
{
    _ubc_get_generic "/proc/bc/$(veid)/resources" "$1" "$2"
}

ubc_get_held() { ubc_get_generic "$1" 3; }
ubc_get_maxheld() { ubc_get_generic "$1" 4; }

ubc_get_fail()
{
    local fail=$(ubc_get_generic "$1" 5)
    local oldfail=0

    local cache="/var/cache/alterator/mkve/proc/bc/$(veid)/resources"
    if [ -f "$cache" ]; then
        oldfail=$(_ubc_get_generic "$cache" "$1" 5)
    fi

    printf '%s' "$(( $fail - $oldfail ))"
}

write_ubc_status()
{
    local cache="/var/cache/alterator/mkve/proc/bc/$(veid)/resources"
    mkdir -p "$(dirname "$cache")"
    cp "/proc/bc/$(veid)/resources" "$cache"

    # I don't care if it isn't created by us. I just append what I want.
    local mount_script="/etc/vz/conf/$(veid).mount"
    if [ -x "$mount_script" ]; then
        grep -q "rm -f \"$cache\"" "$mount_script" ||
        echo "rm -f \"$cache\"" >> "$mount_script"
    else
        cat >"$mount_script" <<EOF
#! /bin/sh

rm -f "$cache"
EOF
    chmod +x "$mount_script"
    fi
}

ubc_get_status()
{
    local maxheld="$1" barrier="$2" fail="$3"
    local gap="95/100"

    barrier="$(echo "scale=0; $barrier*$gap" | bc -q)"
    if [ "$barrier" -eq 0 -o "$maxheld" -lt "$barrier" ]; then
            echo "OK::"
    elif [ "$maxheld" -ge "$barrier" -a "$fail" -eq 0 ]; then
            echo ":WARNING:"
    else
            echo "::ERROR"
    fi
}

list_ubc()
{
    array_of_ubc_barriers=
    array_of_ubc_limits=
    local fake_proc_bc_rs=$(fake_proc_bc_resources)

    while read name barrier limit; do
        array_of_ubc_barriers="$array_of_ubc_barriers $name=$barrier"
        array_of_ubc_limits="$array_of_ubc_limits $name=$limit"

        local held="$(ubc_get_held "$name")"
        local maxheld="$(ubc_get_maxheld "$name")"
        local fail=""$(ubc_get_fail "$name")""
        local status="$(ubc_get_status "$maxheld" "$barrier" "$fail")"
        write_table_item             \
            name    "$name"          \
            barrier "$barrier"       \
            limit   "$limit"         \
            held    "$held"          \
            maxheld "$maxheld"       \
            status_ok "${status%%:*}" \
            status_warning "$(echo $status | cut -d: -f2)" \
            status_error "${status##*:}" \
            #
    done <<< "$fake_proc_bc_rs"
}

write_ubc()
{
    local err= params=
    local b="${in_barrier//;/ }"
    local l="${in_limit//;/ }"

    while [ -n "$b" ]; do
        read x1 b <<< "$b"
        read x2 l <<< "$l"
        read y1 array_of_ubc_barriers <<< "$array_of_ubc_barriers"
        read y2 array_of_ubc_limits <<< "$array_of_ubc_limits"

        [ $x1 != ${y1##*=} -o $x2 != ${y2##*=} ] &&
        [ "$x1" -le "$x2" ] &&
        params="$params --${y1%%=*} $x1:$x2"
    done

    if [ -n "$params" ]; then
        err=$(vzctl set $(veid) $params --save 2>&1) ||
        write_error "$err"
    fi
}

# }}}
# quotas {{{

quota_show_used()
{
    local ret=$(
        vzquota stat $(veid) 2>/dev/null | # get quota statistics, ignore errors
        head -"$1" | tail -1 |             # get $1-th line
        sed 's/ \+/ /g' | cut -f3 -d' '    # get `usage` field
    )

    printf '%s' "${ret:-0}"
}

quota_diskspace_used(){ quota_show_used 2; }
quota_diskinodes_used(){ quota_show_used 3; }

list_quotas()
{
    local diskspace="$(read_param DISKSPACE)"
    local diskinodes="$(read_param DISKINODES)"

    diskspace_softlimit="${diskspace%%:*}"
    diskspace_hardlimit="${diskspace##*:}"
    diskinodes_softlimit="${diskinodes%%:*}"
    diskinodes_hardlimit="${diskinodes##*:}"

    write_table_item                                \
        'quota'     "`_ "disk space in 1k-blocks"`" \
        'softlimit' "$diskspace_softlimit"          \
        'hardlimit' "$diskspace_hardlimit"          \
        'used'      "$(quota_diskspace_used)"       \
        #
    write_table_item                                \
        'quota'     "`_ "disk inodes"`"             \
        'softlimit' "$diskinodes_softlimit"         \
        'hardlimit' "$diskinodes_hardlimit"         \
        'used'      "$(quota_diskinodes_used)"      \
        #
}

write_quotas()
{
    local ds dh is ih cmd=

    if [ "$in_quota_checkbox" == "#t" ]; then
        write_param 'DISK_QUOTA' "yes"
    else
        write_param 'DISK_QUOTA' "no"
        return
    fi

    read ds is <<< "$(echo $in_softlimit | sed -e 's/;/ /g')"
    read dh ih <<< "$(echo $in_hardlimit | sed -e 's/;/ /g')"

    [ "$ds" != "$diskspace_softlimit" -o "$dh" != "$diskspace_hardlimit" ] &&
        cmd="$cmd --diskspace $ds:$dh"
    [ "$is" != "$diskinodes_softlimit" -o "$ih" != "$diskinodes_hardlimit" ] &&
        cmd="$cmd --diskinodes $is:$ih"
    [ "$in_quota_expire" != "$(read_param QUOTATIME)" ] &&
        cmd="$cmd --quotatime $in_quota_expire"
    if [ -n "$cmd" ]; then
        err=$(vzctl set $(veid) $cmd --save) ||
        write_error "$err"
    fi
}

is_quota_enabled()
{
    local dq=$(read_param DISK_QUOTA)
    [ -n "$dq" ] ||
        dq=$( ( . /etc/vz/vz.conf; echo $DISK_QUOTA ) )
    echo $dq
}

# }}}
# capabilities {{{

CAPS="  chown               \
        dac_override        \
        dac_read_search     \
        fowner              \
        fsetid              \
        kill                \
        setgid              \
        setuid              \
        linux_immutable     \
        net_bind_service    \
        net_broadcast       \
        net_admin           \
        net_raw             \
        ipc_lock            \
        ipc_owner           \
        sys_module          \
        sys_rawio           \
        sys_chroot          \
        sys_ptrace          \
        sys_pacct           \
        sys_admin           \
        sys_boot            \
        sys_nice            \
        sys_resource        \
        sys_time            \
        sys_tty_config      \
        mknod               \
        lease               \
        setveid             \
        ve_admin            \
      "

set_capabilities()
{
    # The purpose of that function is to run vzctl like this:
    #     vzctl set 100 --capability "$2:$1 $3:$1" --save
    local on_off=$1; shift
    local caps=

    # Now we can check if there are at least one presented capability
    # and srote them if they are presented
    [ -n "$1" ] || return
    while [ -n "$1" ]; do caps="$caps $1:$on_off"; shift; done

    # We can't check a return value from vzctl, because of
    # if will be always nonzero on a running container
    vzctl set $(veid) --capability "$caps" --save >/dev/null 2>&1
}

write_caps()
{
    # Variables capabilities and in_capabilities store old
    # and new lists of capabilities respectively
    [ "$capabilities" = "$in_capabilities" ] && return

    # We can look now for added and removed capabilities, but we can simplify
    # this by calling vzctl twice: first for unsetting old capabilities, second
    # for setting new. For axample, if
    #    capabilities='a;b;c' in_capabilities='c;d;e',
    # then we run
    #    vzctl a:off b:off c:off
    # to clear mask, and
    #    vzctl c:on d:on e:on
    # to setup new
    set_capabilities off ${capabilities//;/ }
    set_capabilities on ${in_capabilities//;/ }
}

check_capability()
{
    local cap=$(echo "$1" | sed 's/\(.*\)/\U\1/g')
    local caps=$(read_param CAPABILITY)

    while [ -n "$caps" ] ; do
        local c
        read c caps <<< "$caps"
        if [ "${c%%:*}" == "$cap" ]; then
            [ "${c##*:}" == "on" ] && return 0 || return 1
        fi
    done

    return 1
}

list_caps()
{
    local cap

    for cap in $CAPS; do write_enum_item "$cap"; done
}

read_caps()
{
    capabilities=
    local cap

    for cap in $CAPS; do check_capability $cap && capabilities="${capabilities}${cap};"; done
    write_string_param 'capabilities' "${capabilities%;}"
}

# }}}

list_alterator_DNAT()
{
    local enum="$1"
    local ip="$2"
    [ -z "$enum" -o -z "$ip" ] && return

    # e.g., record=tcp:10.1.1.145:8081:192.0.2.7:8080
    for record in $(iptables_helper dnat list | tr '\t' ':'); do
        local rip=$(echo "$record" | cut -d: -f4)
        local rport=$(echo "$record" | cut -d: -f5)
        if [ "$rip" = $ip -a "$rport" = 8080 ]; then
            local oip=$(echo "$record" | cut -d: -f2)
            local oport=$(echo "$record" | cut -d: -f3)
            local where="https://$oip:$oport"
            write_table_item "name" "$where" 'description' "$where"
        fi
    done

}

list_alterator_ip()
{
    local ip
    for ip in $(read_param IP_ADDRESS); do
        local where="https://$ip:8080"
        write_table_item 'name' "$where" 'description' "$where"
        list_alterator_DNAT 'name' "$ip"
    done
}

# action list {{{
action_list()
{
    case ${in__objects} in
        list_resources)         list_ubc             ;;
        list_quotas)            list_quotas          ;;
        list_devices)           list_devices         ;;
        list_caps)              list_caps            ;;
        list_general_options)   list_general_options ;;
        list_ip_address)        list_openvz_param IP_ADDRESS   ;;
        list_interfaces)        list_interfaces ;;
        list_alterator_ip)      list_alterator_ip ;;
    esac
}
# }}}
# action write {{{
action_write()
{
    if [ -n "$in_reset" ]; then
        write_error "`_ "Reset to defaults values"`"
        return
    fi

    case ${in__objects} in
        /)      write_general ;;
        ubc) write_ubc ;;
        ubc_status) write_ubc_status ;;
        quota)  write_quotas ;;
        caps)   write_caps ;;
    esac
}
# }}}
# action read {{{
action_read()
{
    write_string_param 'machine' "$machine"

    case "$in__objects" in
        "/")
            write_string_param 'hostname' "$(read_param HOSTNAME)"
            write_string_param 'general_options' "$in_general_options"
            ;;
        "quota" )
            write_string_param 'quota_expire' "$(read_param QUOTATIME)"
            write_bool_param 'quota_checkbox' "$(is_quota_enabled)"
            ;;
        "caps" )
            read_caps
            ;;
        "ip")
            local ip="$(read_param IP_ADDRESS | sed 's/ /, /g')"
            [ -n "$ip" ] || ip="`_ "not found"`"
            write_string_param 'ip' "$ip"
            ;;
    esac
}
# }}}

# globalise {{{
globalise()
{
    [ -n "$in_machine" ] && machine="$in_machine"
}
# }}}

on_message()
{
    globalise

    set | egrep '^in_'
    echo ------------------------------

    case "$in_action" in
        list)        action_list        ;;
        write)       action_write       ;;
        read)        action_read        ;;
        type)        write_nop          ;;
        *)           write_bool 'false' ;;
    esac
}

message_loop

# vim:fdm=marker et sw=4 ts=4
