#!/usr/bin/env bash
umask 0027
set -uo pipefail

#------------------------------------------------------------------
# SAP HANA OS checks - Library Functions
#------------------------------------------------------------------
# Script name: "saphana-helper-funcs"
#
#------------------------------------------------------------------

# Everything that is for global access after sourcing this script
# has got the prefix LIB_.

# return if saphana-helper-funcs already loaded
[[ -n "${HANA_HELPER_PROGVERSION:-}" ]] && return 0

HANA_HELPER_PROGVERSION='loaded'

##################################################
# Global functions - to be used in other scripts
##################################################

# Returns 1 on NON-ROOT, 0 as ROOT
function LIB_FUNC_IS_ROOT {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ ${UID} -eq 0 ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_CHECK_CHECK_SECURITY {

    local fullpath=$1

    if [[ ! -e "${fullpath}" ]]; then
        printf 'Check file <%s> does not exist.\n'        "${fullpath}"
        return 1
    fi

    local -i fileownerid
    fileownerid=$(stat -c %u "${fullpath}")
    if [[ "${fileownerid}" -ne 0 ]]; then
        printf 'Check file <%s> is not owned by root.\n'  "${fullpath}"
        return 1
    fi

    local fileperm
    fileperm=$(stat -c %A "${fullpath}")
    if [[ "${fileperm}" != ?r???-??-? ]]; then
        printf 'Check file <%s> is world writable.\n'     "${fullpath}"
        return 1
    fi
}

# Returns 1 on Virtualization, 0 on Bare-Metal
function LIB_FUNC_IS_BARE_METAL {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ ( -z "${LIB_PLATF_VIRT_HYPER:-}" ) || ( "${LIB_PLATF_VIRT_HYPER:-}" == 'none' ) ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on VMware, 1 others
function LIB_FUNC_IS_VIRT_VMWARE {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VIRT_HYPER:-}" == 'VMware'* ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on Xen, 1 others
function LIB_FUNC_IS_VIRT_XEN {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VIRT_HYPER:-}" == 'Xen' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on KVM, 1 others
function LIB_FUNC_IS_VIRT_KVM {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VIRT_HYPER:-}" == 'KVM' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on MS HyperV, 1 others
function LIB_FUNC_IS_VIRT_MICROSOFT {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VIRT_HYPER:-}" == 'Microsoft' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on Intel, 1 on other
function LIB_FUNC_IS_INTEL {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_CPU_VENDOR:-}" == 'GenuineIntel' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on AMD, 1 on other
function LIB_FUNC_IS_AMD {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_CPU_VENDOR:-}" == 'AuthenticAMD' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on x86_64, 1 on other
function LIB_FUNC_IS_X64 {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_ARCHITECTURE:-}" == 'x86_64' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on Power, 1 on other
function LIB_FUNC_IS_IBMPOWER {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_ARCHITECTURE:-}" =~ ppc64le|ppc64|powerpc64le ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on PMEM, 1 on other
function LIB_FUNC_IS_NVM_PMEM {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ ${LIB_PLATF_PMEM_MiB:-} -ne 0 ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Returns 0 on Nutanix AHV, 1 on other
function LIB_FUNC_IS_NUTANIX_AHV {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Nutanix' ]] &&
        [[ "${LIB_PLATF_NAME:-}" == 'AHV' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Hyperconverged Clouds
# Returns 0 on TRUE, 1 FALSE
function LIB_FUNC_IS_CLOUD_ALIBABA {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Alibaba Cloud ECS' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_AMAZON {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Amazon EC2' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_GOOGLE {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Google GCP' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_IBM {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'IBM Cloud'* ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_MICROSOFT {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Microsoft Azure' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_SAPCC {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'SAP Converged Cloud' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_IS_CLOUD_HUAWEI {

    local -i _retval=1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    [[ "${LIB_PLATF_VENDOR:-}" == 'Huawei Cloud' ]] && _retval=0

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

# Compares two version strings
# Two version strings as parameters.
# Echos & returns 0 if equal, 1 if first is higher, 2 if second is higher
# - no external utilities - factor 10x faster than original (tr,grep are quite expansive)
function LIB_FUNC_COMPARE_VERSIONS {

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    local -i _retval=0
    local version1
    local version2
    version1=$(LIB_FUNC_TRIM "$1")
    version2=$(LIB_FUNC_TRIM "$2")

    #${1//\-/\.} - Variable Substitution (faster then tr,sed or grep) - replace - by .
    #required for 2.11.3-17.95.2 --> 2.11.3.17.95.2
    version1=${version1//\-/\.}
    version2=${version2//\-/\.}

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # compare <${version1}> to <${version2}>"

    if [[ "${version1}" == "${version2}" ]]; then
        _retval=0
    else

        #to_array, split by .
        local IFS=.
        local -a ver1
        local -a ver2
        read -r -a ver1 <<< "${version1}"
        read -r -a ver2 <<< "${version2}"

        local -i i
        # fill empty fields in ver1 with zeros
        for ((i=0; i<${#ver2[@]}; i++)); do
            [[ -z ${ver1[i]:-} ]] && ver1[i]=0
        done

        for ((i=0; i<${#ver1[@]}; i++)); do

            # fill empty fields in ver2 with zeros
            [[ -z ${ver2[i]:-} ]] && ver2[i]=0

            if ((10#${ver1[i]} > 10#${ver2[i]})); then
                _retval=1
                break
            fi
            if ((10#${ver1[i]} < 10#${ver2[i]})); then
                _retval=2
                break
            fi
        done

    fi

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_COMPARE_TOOBIG_NUMBERS {
# Two large numbers as string parameters.
# returns 0 if equal, 1 if first is higher, 2 if second is higher

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    local -i _retval=-1
    local number1
    local number2
    number1="$1"
    number2="$2"

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # compare <${number1}> to <${number2}>"

    if [[ "${number1}" == "${number2}" ]]; then
        _retval=0

    elif [[ ${#number1} -gt ${#number2} ]]; then
        _retval=1

    elif [[ ${#number1} -lt ${#number2} ]]; then
        _retval=2

    elif [[ ${#number1} -le 18  ]]; then

        _retval=$(( number1 > number2 ? 1 : 2 ))

    else

        #too large to be handled as integers; split and compare high/low part
        local -i number1part
        local -i number2part

        #higher part
        number1part=${number1:0:(( ${#number1} - 18 ))}
        number2part=${number2:0:(( ${#number2} - 18 ))}

        if [[ ${number1part} -gt ${number2part} ]]; then
            _retval=1

        elif [[ ${number1part} -lt ${number2part} ]]; then
            _retval=2

        else

            #lower part
            number1part=${number1:(-18)}
            number2part=${number2:(-18)}
            _retval=$(( number1part > number2part ? 1 : 2 ))

        fi

    fi

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # RC=${_retval}"
    return ${_retval}
}

function LIB_FUNC_TRIM_LEFT {
    # remove leading whitespace characters
    printf '%s' "${1#"${1%%[![:space:]]*}"}"
}

function LIB_FUNC_TRIM_RIGHT {
    # remove trailing whitespace characters
    printf '%s' "${1%"${1##*[![:space:]]}"}"
}

function LIB_FUNC_TRIM {
    : "$(LIB_FUNC_TRIM_LEFT "$1")"
    : "$(LIB_FUNC_TRIM_RIGHT "$_")"
    printf '%s' "$_"
}

function LIB_FUNC_NORMALIZE_KERNELn {

    local -n __kernel_vers="$1"
    shift 1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    __kernel_vers=${__kernel_vers/PTF-default*/1}   #3.0.101-0.47.71.7930.0.PTF-default
    __kernel_vers=${__kernel_vers/default*/1}       #3.0.101-0.47.71-default
    __kernel_vers=${__kernel_vers/bigsmp*/1}        #3.0.101-0.47-bigsmp
    __kernel_vers=${__kernel_vers/bigmem*/1}        #3.0.101-88-bigmem
    __kernel_vers=${__kernel_vers/ppc64*/1}         #3.0.101-71-ppc64
    __kernel_vers=${__kernel_vers/azure/1}          #4.12.14-6.12-azure"
    __kernel_vers=${__kernel_vers/.el[[:digit:]]*/} # Remove trailing ".el6.x86_64" or ".el7.ppc64le"

}

function LIB_FUNC_NORMALIZE_KERNEL {

    local kernelversion="$1"
    shift 1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    kernelversion=${kernelversion/PTF-default*/1}   #3.0.101-0.47.71.7930.0.PTF-default
    kernelversion=${kernelversion/default*/1}       #3.0.101-0.47.71-default
    kernelversion=${kernelversion/bigsmp*/1}        #3.0.101-0.47-bigsmp
    kernelversion=${kernelversion/bigmem*/1}        #3.0.101-88-bigmem
    kernelversion=${kernelversion/ppc64*/1}         #3.0.101-71-ppc64
    kernelversion=${kernelversion/azure/1}          #4.12.14-6.12-azure"
    kernelversion=${kernelversion/.el[[:digit:]]*/} # Remove trailing ".el6.x86_64" or ".el7.ppc64le"

    LIB_FUNC_NORMALIZE_KERNEL_RETURN="${kernelversion}" #variable is available to caller
}

function LIB_FUNC_NORMALIZE_RPMn {

    local -n __rpmversion="$1"
    shift 1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    __rpmversion=${__rpmversion/.el/.}     # 2.17-157.el7_3.5 -> 2.17-157.7_3.5
    __rpmversion=${__rpmversion//_/.}      # 2.17-157.7_3.5 -> 2.17-157.7.3.5
    __rpmversion=${__rpmversion//+*-/-}    # 2.1.5+20221208.a3f44794f-150500.6.11.1 -> 2.1.5-150500.6.11.1

}

function LIB_FUNC_NORMALIZE_RPM {

    local rpmversion="$1"
    shift 1

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    rpmversion=${rpmversion/.el/.}     # 2.17-157.el7_3.5 -> 2.17-157.7_3.5
    rpmversion=${rpmversion//_/.}      # 2.17-157.7_3.5 -> 2.17-157.7.3.5
    rpmversion=${rpmversion//+*-/-}    # 2.1.5+20221208.a3f44794f-150500.6.11.1 -> 2.1.5-150500.6.11.1

    LIB_FUNC_NORMALIZE_RPM_RETURN="${rpmversion}"   #variable is available to caller
}


function LIB_FUNC_STRINGCONTAIN {

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    #$1=string $2=substring
    #case "$1" in *"$2"* ) return 0;;esac
    #return 1
    [[ -z "${1##*"$2"*}" ]] && [[ -z "$2" || -n "$1" ]];
}

##########################################################
# Non-global functions - not to be used in other scripts
##########################################################

function __get_platform_mem_details {

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    local collectedby
    collectedby='lib_platf_*'

    #LIB_PLATF_RAM_MIB_PHYS - so far no reliable way detecting physMEM on all platforms

    if [[ -z ${LIB_PLATF_RAM_MiB_AVAILABLE:-} ]]; then

        #MemTotal in /proc/meminfo - usable memory
        LIB_PLATF_RAM_MiB_AVAILABLE=$(awk '/^MemTotal:/ {match($0, /[0-9]+/); printf "%.0f", substr($0,RSTART,RLENGTH)/1024 }' /proc/meminfo)

    fi

    if [[ -z ${LIB_PLATF_RAM_MIB_PHYS:-} || ${LIB_PLATF_RAM_MIB_PHYS} -eq 0 ]]; then

        LIB_PLATF_RAM_MIB_PHYS=${LIB_PLATF_RAM_MiB_AVAILABLE:-}
        collectedby='meminfo'

    fi

    readonly LIB_PLATF_RAM_MIB_PHYS
    readonly LIB_PLATF_RAM_MiB_AVAILABLE

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # LIB_PLATF_RAM_MIB_PHYS (${collectedby:-}): ${LIB_PLATF_RAM_MIB_PHYS:-}; LIB_PLATF_RAM_MiB_AVAILABLE ${LIB_PLATF_RAM_MiB_AVAILABLE:-}"
}

function __get_platform_pmem_details {

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    local -i pmem_decive_memory

    if [[ -z ${LIB_PLATF_PMEM_MiB:-} ]]; then

        #780335104 pmem0
        while read -r pmem_device ; do

            logTrace "PMEM device <${pmem_device}>"

            [[ ${pmem_device} =~ ([0-9]+) ]]
            pmem_decive_memory=${BASH_REMATCH[1]:-} #780335104

            LIB_PLATF_PMEM_MiB+=$(( pmem_decive_memory/1024 ))

        done <<< "$(grep -Eo '[[:digit:]]+ pmem[[:digit:]]+$' /proc/partitions)"

    fi
    readonly LIB_PLATF_PMEM_MiB

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # LIB_PLATF_PMEM_MiB: ${LIB_PLATF_PMEM_MiB:-}"

}

#============================================================
# LIB MAIN - initialization
#============================================================
function _lib_helper_main {

    logTrace "<${BASH_SOURCE[0]}:${FUNCNAME[*]}>"

    logDebug "<${BASH_SOURCE[0]}:${FUNCNAME[0]}> # HOSTTYPE - PLATFORM (${HOSTTYPE})"

    case "${HOSTTYPE}" in

        'x86_64')
                    #shellcheck source=./lib_platf_x86_64
                    source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib_platf_x86_64" || { echo 'unable to load lib_platf_x86_64 library' >&2; exit 1; }
        ;;

        'ppc64'* | 'powerpc64'* )
                    #shellcheck source=./lib_platf_power
                    source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib_platf_power" || { echo 'unable to load lib_platf_power library' >&2; exit 1; }
        ;;

        *)
                    #unsupported platform
                    logError "Unsupported platform - <${HOSTTYPE}>"
                    exit 2
        ;;

    esac

    __get_platform_mem_details
    __get_platform_pmem_details

    #shellcheck source=./lib_hana
    source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib_hana" || { echo 'unable to load lib_hana library' >&2; exit 1; }
}

##########################################################
#GLOBAL -x --> exported
declare     LIB_PLATF_VENDOR
declare     LIB_PLATF_NAME
declare     LIB_PLATF_CPU_VENDOR
declare     LIB_PLATF_CPU
declare -i  LIB_PLATF_CPU_MODELID
declare -i  LIB_PLATF_CPU_STEPID
declare -i  LIB_PLATF_RAM_MIB_PHYS
declare -i  LIB_PLATF_RAM_MiB_AVAILABLE
declare -i  LIB_PLATF_PMEM_MiB

declare     LIB_PLATF_ARCHITECTURE
declare -i  LIB_PLATF_CPU_THREADSPERCORE
declare -i  LIB_PLATF_CPU_CORESPERSOCKET
declare -i  LIB_PLATF_CPU_SOCKETS
declare -i  LIB_PLATF_CPU_NUMANODES

declare     LIB_PLATF_VIRT_HYPER
declare     LIB_PLATF_VIRT_TYPE
declare     LIB_PLATF_VIRT_VM_VERSION

#LIB local

#Shellcheck - get rid of variables unused - use or export - warnings
#shellcheck source=./lib_platf_x86_64

#Import libraries
#shellcheck source=./saphana-logger
source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/saphana-logger" || { echo 'unable to load saphana-logger library' >&2; exit 1; }
#shellcheck source=./lib_linux_release
source "$( cd "${BASH_SOURCE[0]%/*}" && pwd )/lib_linux_release" || { echo 'unable to load lib_linux_release library' >&2; exit 1; }

_lib_helper_main
