#!/bin/sh

# Print supported releases----------------------------------
__print_release () {
    supported="15.0-CURRENT
               14.0-RELEASE
               13.2-RELEASE"

    echo "Supported releases are: "
    for rel in $(echo $supported) ; do
        # hack to get printf to both left adjust and have spaces.
        printf "%1s %-s \n" "" "$rel"
    done
}

# Print defaults set in this script.
__print_defaults () {
    local _prop _prop_name _saved_default

    _CONF="$CONF_NET
           $CONF_JAIL
           $CONF_CUSTOM
           $CONF_SYNC
           $CONF_FTP
           $CONF_GIT
           $CONF_ZFS
           $CONF_RCTL"

    for _prop in $(echo $_CONF)  ; do
        _prop_name=$_prop
        eval _prop="\$${_prop}"

        if [ ! -z "${_prop}" ] ; then
            _saved_default="$(__get_jail_prop ${_prop_name} default)"
            if [ "${_saved_default}" != "null" ] ; then
                echo "${_prop_name}=${_saved_default}"
            else
                echo "${_prop_name}=${_prop}"
            fi
        fi
    done
}

# Get default value for property if one is found
__get_default_prop () {
    local _property _value

    _property="$1"
    _value="$(__get_jail_prop ${_property} default)"

    if [ ! -z "${_value}" ] ; then
        echo "${_value}"
    else
        echo "none"
    fi
}

# Find and return the jail's top level ZFS dataset
__find_jail () {
    local _name _jlist _jail _tag _found _mountpoint _juuid _otag

    _name="$1"
    _found="0"

    # shellcheck disable=SC2154,SC2155
    export jail_datasets="$(zfs list -d3 -rH -o name "${pool}/iocell" \
        | egrep -v "jails$|base|releases|templates$|download|${pool}/iocell$|/root$|/data$")"

    if [ "${_name}" = "ALL" ] ; then
        for _jail in ${jail_datasets} ; do
            _mountpoint="$(zfs get -H -o value mountpoint ${_jail})"

            if [ ! -e "${_mountpoint}/config" ] ; then
                _juuid="$(echo ${_jail} | cut -d / -f 4)"

                if [ "$(id -un)" != "root" ] ; then
                    _otag="$(zfs get -H -o value org.freebsd.iocell:tag ${_jail})"
                    echo "  ERROR: please run as root to migrate ${_otag} to ucl." >&2
                    continue
                elif [ "$(id -un)" = "root" ] ; then
                    __dump_config "${_juuid}"
                    _clone_check=$(zfs get -H origin "${_jail}" | \
                        awk '{print $3}')
                    _jail_type=$(__get_jail_prop type "${_juuid}" "${_jail}")

                    if [ "${_jail_type}" = "jail" ] ; then
                        # Check for thick or clone jails and migrate their type
                        if [ "${_clone_check}" != "-" ] ; then
                            __set_jail_prop type=clonejail "${_juuid}" \
                                "${_jail}"
                        fi
                    fi
                fi
            fi

            echo ${_jail}
        done
    elif [ "$(echo ${_name} | grep "^-")" ] ; then
        __die "${_name} is not a valid command"
    else
        # This means we were given a UUID, skip the transformations below
        # and hardcode it.
        if [ "$(echo ${_name} | wc -c)" -eq 37 ] ; then
            _jlist="${pool}/iocell/jails/${_name}"
            if [ "$(zfs get -H creation ${_jlist})" ] ; then
                _found="1"
            fi
        fi

        if [ "${_found}" != "1" ] ; then
            for _jail in ${jail_datasets} ; do
                _found="$(echo ${_jail} | grep -iE ${_name}$)"
                _mountpoint="$(zfs get -H -o value mountpoint ${_jail})"

                if [ ! -e "${_mountpoint}/config" ] ; then
                    _juuid="$(echo ${_jail} | cut -d / -f 4)"
                    __dump_config ${_juuid}
                fi

                _tag="$(uclcmd get -qf ${_mountpoint}/config tag 2> /dev/null)"
                # _jail is actually a dataset, so fulluuid needs to be faked.
                _fulluuid="$(__check_name "name" ${_jail} 2> /dev/null)"

                if [ "${_tag}" = "${_name}" ] ; then
                    _jlist="${_jlist} ${_jail}"
                    break
                elif [ "${_fulluuid}" = "${_name}" ] ; then
                    _jlist="${_jlist} ${_jail}"
                    break
                elif [ "${#_found}" -gt 0 ] ; then
                    _jlist="${_jlist} ${_jail}"
                fi
            done
        fi

        if [ "$(echo ${_jlist}|wc -w)" -eq "1" ] ; then
            # remove whitespace
            echo "${_jlist}" | xargs
        elif [ "$(echo ${_jlist}|wc -w)" -gt "1" ] ; then
            __die "multiple jails matching name: $_name!"
        fi

        if [ -z "${_jlist}" ] ; then
            __die "${_name} was not found!"
        fi
    fi
}

__list_jails () {
    local _jails _switch _all_jids _ioc_jids _non_ioc_jids _releases \
          _temp_loop_var _state _ip4 _ioc_jailbody _sortby _grepmatch

    _switch=$1
    _all_jids=$(jls -N -h jid | grep -v -x jid )
    _grepmatch='RELEASE$|STABLE$|PRERELEASE$|CURRENT$|BETA$'

    if [ ! -z "${_switch}" ] && [ "${_switch}" = "--warden" ] ; then
        __list_jails_warden
        exit 0
    fi

    if [ ! -z "${_switch}" ] && [ "${_switch}" = "-r" ] ; then
        echo "Downloaded releases:"
        _releases="$(zfs list -o name -Hr "${pool}/iocell/releases" \
                   | egrep ${_grepmatch} | cut -d / -f 4)"
        for _rel in ${_releases} ; do
            # hack to get printf to both left adjust and have spaces.
            printf "%1s %-s \n" "" "${_rel}"
        done
        exit 0
    fi

    if [ ! -z "${_switch}" ] && [ "${_switch}" != "-t" ] && \
        [ "${_switch}" != "-s" ] ; then
        __die "invalid switch ${_switch}"
    fi

    # If the -t switch is given, show templates otherwise show the jails
    if [ "${_switch}" = "-t" ] ; then
        _jails=$(__find_jail "ALL" | \
            awk -v temp="${pool}/iocell/templates" \
            '$0 ~ temp { if (!/releases\/[0-9]/) { print }}')
    else
        _jails=$(__find_jail "ALL" | \
            awk 'BEGIN { FS = "/" } ; { if (length($NF)==36) { print }}')
    fi

    _sortby=""
    # if the -s switch is given then grab what column to sort on
    if [ "${_switch}" = "-s" ]; then
        # convert sortby to lowercase
        _sortby=$(echo "$2" | tr '[:upper:]' '[:lower:]')

        # if the user specified the -s switch and nothing to sort on,
        # then default to jid
        if [ -z "${_sortby}" ]; then
            _sortby="jid"
        fi
    fi

    # initialise the jailbody variable as blank
    _ioc_jailbody=''

    # For every jail we need to get the property
    for _jail in ${_jails} ; do
        # _jail is actually a dataset, so fulluuid needs to be faked.
        _uuid=$(__check_name "name" "${_jail}" 2> /dev/null)
        _boot=$(__get_jail_prop boot "${_uuid}" "${_jail}" 2> /dev/null)
        _tag=$(__get_jail_prop tag "${_uuid}" "${_jail}" 2> /dev/null)
        _type=$(__get_jail_prop type "${_uuid}" "${_jail}" 2> /dev/null)
        _release=$(__get_jail_prop release "${_uuid}" "${_jail}" \
            2> /dev/null)
        # get jid for iocell jails
        _jid=$(jls -j "ioc-${_uuid}" jid 2> /dev/null)

        if [ -z "${_jid}" ] ; then
            _jid="-"
            _ip4="-"
        else
            # get the ipv4 address for this jid
            _ip4=$(jls -j "${_jid}" -h ip4.addr | grep -v -x "ip4.addr")
        fi

        if [ -z "${_uuid}" ] ; then
            break
        fi

        _ioc_jids=${_ioc_jids}" "${_jid}

        if [ "${_jid}" = '-' ] ; then
            _state=down
        else
            _state=up
        fi

        if [ -z "${_switch}" ] ; then
            _switch=zero
        fi

        # echo the lines together
        _ioc_jailbody=$(echo "${_ioc_jailbody}" ;
                      printf "%s^%s^%s^%s^%s^%s^%s^%s\n" \
                      "${_jid}" "${_uuid}" "${_boot}" "${_state}" "${_tag}" \
                      "${_type}" "${_ip4}" "${_release}")
    done

    # if the user wanted to sort, then sort on the relevant column
    case "${_sortby}" in
        jid)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -n -k 1)
        ;;
        uuid)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 2)
        ;;
        boot)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 3)
        ;;
        state)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 4)
        ;;
        tag)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 5)
        ;;
        type)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 6)
        ;;
        ip4)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | \
                          awk -F "^" '{print $7 " % " $0}' | \
                          sort -t. -n -k1,1 -k2,2 -k3,3 -k4,4 | \
                          sed 's/[^%]*% //')
        ;;
        release)
            _ioc_jailbody=$(echo "${_ioc_jailbody}" | sort -t^ -k 8)
        ;;
    esac

    # Setup the column names, this is all piped to column to deal with long TAGs
    # nicely.
    {
        # print out the ioc jails head
        printf "%s^%s^%s^%s^%s^%s^%s^%s\n" \
               "JID" "UUID" "BOOT" "STATE" "TAG" "TYPE" "IP4" "RELEASE"
        # and full ioc jails body, already sorted if requested
        echo "${_ioc_jailbody}"

        # create list of active jids not registered in iocell
        for _all_jail in ${_all_jids} ; do
            for _ioc_jail in ${_ioc_jids} ; do
                if [ "${_all_jail}" = "${_ioc_jail}" ] ; then
                    _temp_loop_var=""
                    break
                else
                    _temp_loop_var="${_all_jail}"

                fi
            done

        if [ -n "${_temp_loop_var}" ] ; then
            _non_ioc_jids=${_non_ioc_jids}" "${_temp_loop_var}
        fi
        done

        # output non iocell jails currently active
        if [ -n "${_non_ioc_jids}" ] ; then
            if [ "${_switch}" != "-t" ] ; then
                printf "%-+40s\n" "--- non iocell jails currently active ---"
                printf "%s^%s^%s^%s\n" "JID" "PATH"\
                      "IP4" "HOSTNAME"
                for _jid in ${_non_ioc_jids} ; do
                    _path=$(jls -j "${_jid}" -h path | grep -v -x "path")
                    _ip4=$(jls -j "${_jid}" -h ip4.addr | grep -v -x "ip4.addr")
                    _host_hostname=$(jls -j "${_jid}"  -h host.hostname | \
                                    grep -v -x "host.hostname")
                    printf "%s^%s^%s^%s\n" "${_jid}" "${_path}"  \
                            "${_ip4}" "${_host_hostname}"
                done
            fi
        fi
    } | column -ts^ # Format the table so everything is all nice
}

__list_jails_warden () {
    local _jails _switch _ioc_jids _uuid _boot _tag _template _interfaces \
          _ip4_addr _ip6_addr _vnet _vnet0_mac \
          _vnet1_mac _vnet2_mac _vnet3_mac _vnet4_mac _vnet5_mac _vnet6_mac \
		  _vnet7_mac _vnet8_mac _vnet9_mac _type

    _jails=$(__find_jail "ALL" | \
            awk 'BEGIN { FS = "/" } ; { if (length($NF)==36) { print }}')

    for _jail in ${_jails} ; do
      # _jail is actually a dataset, so fulluuid needs to be faked.
        _uuid=$(__check_name "name" "${_jail}" 2> /dev/null)
        _boot=$(__get_jail_prop boot "${_uuid}" "${_jail}")
        _tag=$(__get_jail_prop tag "${_uuid}" "${_jail}")
        _template=$(__get_jail_prop istemplate "${_uuid}" "${_jail}")
        _interfaces=$(__get_jail_prop interfaces "${_uuid}" "${_jail}")
        _ip4_addr=$(__get_jail_prop ip4_addr "${_uuid}" "${_jail}")
        _ip6_addr=$(__get_jail_prop ip6_addr "${_uuid}" "${_jail}")
        _vnet=$(__get_jail_prop vnet "${_uuid}" "${_jail}")
        _vnet0_mac=$(__get_jail_prop vnet0_mac "${_uuid}" "${_jail}")
        _vnet1_mac=$(__get_jail_prop vnet1_mac "${_uuid}" "${_jail}")
        _vnet2_mac=$(__get_jail_prop vnet2_mac "${_uuid}" "${_jail}")
        _vnet3_mac=$(__get_jail_prop vnet3_mac "${_uuid}" "${_jail}")
        _vnet4_mac=$(__get_jail_prop vnet4_mac "${_uuid}" "${_jail}")
        _vnet5_mac=$(__get_jail_prop vnet5_mac "${_uuid}" "${_jail}")
        _vnet6_mac=$(__get_jail_prop vnet6_mac "${_uuid}" "${_jail}")
        _vnet7_mac=$(__get_jail_prop vnet7_mac "${_uuid}" "${_jail}")
        _vnet8_mac=$(__get_jail_prop vnet8_mac "${_uuid}" "${_jail}")
        _vnet9_mac=$(__get_jail_prop vnet9_mac "${_uuid}" "${_jail}")
        _type=$(__get_jail_prop type "${_uuid}" "${_jail}")

        # get jid for iocell jails
        _jid=$(jls -j "ioc-${_uuid}" jid 2> /dev/null)
        if [ -z "${_jid}"  ] ; then
            _jid="-"
        fi

        _ioc_jids=${_ioc_jids}" "${_jid}

        if [ "${_jid}" = '-' ] ; then
            _state=down
        else
            _state=up
        fi

        printf "%s\n" "id: ${_jid}" "host: ${_tag}" "ipv4: ${_ip4_addr}" \
               "alias-ipv4: " "bridge-ipv4: " "alias-bridge-ipv4: " \
               "defaultrouter-ipv4: " "ipv6: ${_ip6_addr}" "alias-ipv6: " \
               "bridge-ipv6: " "alias-bridge-ipv6: " "defaultrouter-ipv6: " \
               "autostart: ${_boot}" "vnet: ${_vnet}" "status: ${_state}" \
               "type: ${_type}"
    done
}

__print_disk () {
  local _datasets _fulluuid _compressratio _reservation _quota _used \
        _available _tag

    _datasets=$(__find_jail "ALL")

    {
    printf "%s^%s^%s^%s^%s^%s^%s\n" "UUID" "CRT" "RES" "QTA" "USE" "AVA" "TAG"
        for _dataset in ${_datasets} ; do
            # We only have a dataset so fulluuid needs to be faked.
            _fulluuid=$(__check_name "name" "${_dataset}" 2> /dev/null)
            _tag=$(__get_jail_prop tag "${_fulluuid}" "${_dataset}")
            _compressratio=$(zfs get -H -o value compressratio "${_dataset}")
            _reservation=$(zfs get -H -o value reservation "${_dataset}")
            _quota=$(zfs get -H -o value quota "${_dataset}")
            _used=$(zfs get -H -o value used "${_dataset}")
            _available=$(zfs get -H -o value available "${_dataset}")

            printf "%s^%s^%s^%s^%s^%s^%s\n" "${_fulluuid}" "${_compressratio}" \
                "${_reservation}" "${_quota}" "${_used}" "${_available}" \
                "${_tag}"
        done
    } | column -ts^
}

# Get jail properties
# Expects: property as $1 and UUID|jail name as $2 optionally dataset as $3
__get_jail_prop () {
    local _value _found _name _property _dataset _fulluuid _state _mountpoint \
          _prop

    _name="$2"
    _property="$1"
    _dataset="$3"
    _found="0"

    if [ -z "${_name}" ] ; then
        __die "missing property or UUID/TAG!"
    fi

    if [ "${_property}" = "-r" ] ; then
        __show "${2}"
        return
    fi

    if [ "${_name}" = "default" ] ; then
        uclcmd get -qf ${iocroot}/.default ${_property} 2> /dev/null
        return
    else
        if [ -z "${_dataset}" ] ; then
            _dataset="$(__find_jail ${_name})" || exit $?
        fi

        _mountpoint="$(zfs get -H -o value mountpoint ${_dataset})"

        if [ "${_property}" = "all" ] ; then
            while IFS=" = " read _prop _value ; do
                _value="$(echo ${_value} | sed -e 's/;//g' -e 's/"//g')"
                echo "${_prop}:${_value}"
            done <${_mountpoint}/config

            for _prop in ${CONF_ZFS} ; do
                _value="$(zfs get -H -o value ${_prop} ${_dataset})"
                echo "${_prop}:${_value}"
            done

            return
        fi

        _value="$(uclcmd get -qf ${_mountpoint}/config ${_property} \
            2> /dev/null)"
    fi

    # If they don't supply state, then it's a normal property to be returned.
    if [ "${_property}" != "state" ] ; then
        # These are our properties
        if [ "${_property}" = "sysvmsg" -o "${_property}" = "sysvsem" \
            -o "${_property}" = "sysvshm" ] ; then
            if [ "${_value}" = "null" ] ; then
                _found=1
                echo "new"
            fi
        fi

        if [ "${_value}" != "null" ] ; then
            _found=1
            echo "${_value}"
        # For any ZFS native properties
        elif [ "${_value}" = "null" ] ; then
            _value="$(zfs get -H -o value ${_property} ${_dataset} 2> \
                /dev/null)"

            if [ $? -eq 0 ] ; then
                _found=1
                echo "${_value}"
            fi
        fi
    else
        _template="$(__get_jail_prop istemplate $_name $_dataset)"
        if [ "${_template}" != "yes" ]; then
            _fulluuid="$(uclcmd get -qf ${_mountpoint}/config host_hostuuid 2> \
                /dev/null)"
            _state="$(__is_running ${_fulluuid})"
        else
            _state="$(__is_running ${_name})"
        fi

        if [ "${_state}" ] ; then
            echo "up"
        else
            echo "down"
        fi
        exit 0
    fi

    if [ "${_found}" -ne "1" ] ; then
        __die "unsupported property: ${_property}!"
    fi
}

__runtime () {
    local _name _dataset _fulluuid _state _params

    _name=$1

    if [ -z "${_name}" ] ; then
        __die "missing UUID!"
    fi

    _dataset="$(__find_jail ${_name})" || exit $?

    if [ -z "${_dataset}" ] ; then
        __die "${_name} not found!"
    fi

    _fulluuid="$(__check_name ${_name} ${_dataset})"

    _state="$(jls -n -j ioc-${_fulluuid} | wc -l)"

    if [ "${_state}" -eq "1" ] ; then
        _params="$(jls -nj ioc-${_fulluuid})"
        for _i in ${_params} ; do
            echo "  ${_i}"
        done
    else
        __die "jail ${_fulluuid} is not up."
    fi
}

__get_jail_name () {
    for i in $@; do
        :;
    done

    echo $i
}

# search for executable prestart|poststart|prestop|poststop in jail_dir first,
# else use jail exec_<type> property unchanged
__findscript () {
    local _fulluuid _dataset _type _jail_path
    _fulluuid="$1"
    # type should be one of prestart|poststart|prestop|poststop
    _type="$2"
    _dataset="$3"
    _jail_path="$(__get_jail_prop mountpoint $_fulluuid $_dataset)"

    if [ -x "${_jail_path}/${_type}" ]; then
        echo "${_jail_path}/${_type}"
    else
        echo "$(__get_jail_prop exec_${_type} $_fulluuid $_dataset)"
    fi
}

# Expects name and optional dataset as $2
# translates names/tags to UUID
__check_name () {
    local _name _dataset _uuid

    _name="$1"

    if [ -z "${_name}" ] ; then
        echo "ERROR" >&2
        return 1
    fi

    if [ "$2" ] ; then
        _dataset="$2"
        _uuid="$(echo ${_dataset} | cut -d / -f 4)"
    else
        _dataset="$(__find_jail ${_name})" || exit $?
        _uuid="$(__get_jail_prop host_hostuuid ${_name} ${_dataset})"
    fi

    echo "${_uuid}"
}

# Expects full UUID
# Checks whether jail is up
__is_running () {
    local _fulluuid _state
    _fulluuid="$1"
    _state="$(jls -j ioc-${_fulluuid} jid 2> /dev/null)"

    if [ ! -z "${_state}" ] ; then
        echo 1
    fi
}
