#!/bin/bash

################################################################################
# Copyright (C) 2012-2026 NICE s.r.l.
# All Rights Reserved
#
# This software is the confidential and proprietary information
# of NICE s.r.l. ("Confidential Information").
# You shall not disclose such Confidential Information
# and shall use it only in accordance with the terms of
# the license agreement you entered into with NICE.
################################################################################

NVIDIA_MIN_VERSION="340.24"

usage() {
    echo "Usage: $0 [-l logfile] [xorg.conf path]"
    echo
    exit 1
}

if [ $# -gt 4 ]; then
    usage
fi

if [ "$1" = "-l" ]; then
    shift

    LOG_FILE="$1"
    shift
fi

if [ "$1" != "" ]; then
    X_CONF="$1"

    if [ ! -r "${X_CONF}" ]; then
        echo "File ${X_CONF} cannot be read"
        usage
    fi

    shift
else
    X_CONF="/etc/X11/xorg.conf"
fi

check() {
    local name="$1"
    local description="$2"
    local command="$3"
    local type="$4"
    local message="$5"
    local hint="$6"

    printf "."

    local res="SUCCESS"
    local command_output=
    command_output=$(eval "$command")
    local exit_code=$?
    if [ ${exit_code} -ne 0 ]; then
        res="ERROR\n\n$message"
    fi

    log "------------------------------------------------------------------"
    log "TEST: $name ($type)"
    log "------------------------------------------------------------------"
    log
    log "$description"
    log
    log "Test Output (exit code: ${exit_code}):\n"
    log "[${USER}@`hostname` `basename ${PWD}`]# $command"
    log "$command_output"
    log
    log "Test Result: $res"
    log
    if [ "$res" != "SUCCESS" -a -n "$hint" ]; then
        log "$hint"
        log
    fi
    log

    if [ "$res" != "SUCCESS" ]; then
        local err_file=${WARNINGS_FILE}
        if [ "$type" = "E" ]; then
          err_file=${ERRORS_FILE}
          ERRORS=$((ERRORS + 1))

          echo "ERROR (${ERRORS}/%NUM_ERRORS%)" >> "${err_file}"
        elif [ "$type" = "W" ]; then
          WARNINGS=$((WARNINGS + 1))
          echo "WARNING (${WARNINGS}/%NUM_WARNINGS%)" >> "${err_file}"
        else
          err_file=${INFO_FILE}
          INFO=$((INFO + 1))
          echo "INFO (${INFO}/%NUM_INFO%)" >> "${err_file}"
        fi

        echo >> "${err_file}"

        echo -e "  ${message}" >> "${err_file}"
        echo >> "${err_file}"

        if [ -n "${hint}" ]; then
            echo -e "  ${hint}" >> "${err_file}"
            echo >> "${err_file}"
        fi

        echo >> "${err_file}"
    fi
    return ${exit_code}
}

get_system () {
    declare -a distro_patterns=(
        "rhel:^\(Red Hat Enterprise Linux .*release\) \+\([0-9.]\+\).*"
        "oracle:\(Oracle Linux Server release\) \+\([0-9.]\+\).*"
        "centos:^\(CentOS .*\) \+\([0-9.]\+\).*",
        "rocky:^\()Rocky Linux\) +\([0-9.]\+\).*"
    )

    if [ -f "/etc/os-release" ]; then
        local distro=$(cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '"')
        local version=$(cat /etc/os-release | grep '^VERSION_ID=' | cut -d'=' -f2 | tr -d '"')
    elif [ -f "/etc/redhat-release" ]; then
        for distro_pattern in "${distro_patterns[@]}"; do
            local distro="${distro_pattern%:*}"
            local pattern="${distro_pattern#*:}"
            local version=$(cat /etc/redhat-release | sed -e 's/'"$pattern"'/\2/')
            if [ -n "$version" ]; then
                break
            fi
        done
    fi
    if [ -z "$distro" -o -z "$version" ]; then
        log "Could not detect distro and/or version"
    fi

    if [ "$distro" = ol ]; then
        distro="oracle"
    fi

    echo "$distro;$version"
}

get_system_pretty_version () {
    local pretty_version

    if [ -f "/etc/redhat-release" ]; then
        pretty_version=$(cat "/etc/redhat-release" | head -1)
    elif [ -f "/etc/SuSE-release" ]; then
        pretty_version=$(cat "/etc/SuSE-release" | head -1)
    elif $(command -v lsb_release 2>&1 > /dev/null); then
       pretty_version=$(lsb_release -d | cut -d':' -f2 | tr -d '\t')
    elif [ -f "/etc/os-release" ]; then
        pretty_version=$(cat /etc/os-release | grep "PRETTY_NAME" | cut -d'=' -f2 | tr -d '"')
    else
        log "Unable to retrieve the system pretty version!"
    fi

    echo "$pretty_version"
}

cmp_versions() {
    test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1";
}

check_system() {
    local _data=$(get_system)
    local _system=${_data%;*}
    local _os_version=${_data#*;}
    local _major_version=${_os_version//.*}
    local _supported=yes

    case "${_system}" in
        fedora)
            log "Detected Fedora system"
            DCV_SYSTEM_TYPE="rhel_7"
            return 0
            ;;
        amzn)
            if [ "$_major_version" -eq 2 ]; then
                log "Detected Amazon Linux 2 system"
                DCV_SYSTEM_TYPE="rhel_7"
                return 0
            elif [ "$_major_version" -eq 2023 ]; then
                log "Detected Amazon Linux 2023 system"
                DCV_SYSTEM_TYPE="amzn_2023"
                return 0
            else
                _supported=no
            fi
            ;;
        centos | rhel | oracle | rocky)
            if [ "$_major_version" -ge 8 -a "$_major_version" -le 9 ]; then
                log "Detected RHEL or CentOS or Oracle or Rocky system"
                DCV_SYSTEM_TYPE="rhel_${_major_version}"
                return 0
            else
                _supported=no
            fi
            ;;
        debian)
            if [ "$_major_version" -eq 12 ]; then
                log "Detected Debian system"
                    DCV_SYSTEM_TYPE="debian_${_major_version}"
                    return 0
            else
                _supported=no
            fi
            ;;
        sles | sle-hpc | sled)
            if [ "$_major_version" -ge 12 -a "$_major_version" -le 15 ]; then
                log "Detected SUSE system"
                DCV_SYSTEM_TYPE="sles_${_major_version}"
                return 0
            else
                _supported=no
            fi
            ;;
        ubuntu)
            if [ "$_major_version" -ge 20 -a "$_major_version" -le 24 ]; then
                log "Detected Ubuntu system"
                    DCV_SYSTEM_TYPE="ubuntu_${_major_version}"
                    return 0
            else
                _supported=no
            fi
            ;;
        *)
            log "Unable to detect system"
            return 1
            ;;
    esac

    if [ "${supported}" = "no" ]; then
        log "Detected unsupported version ${major_version} for ${_os_version} distribution"
    fi
}

check_selinux() {
    selinuxenabled && return 1 || return 0
}

check_nouveau() {
    lsmod | grep -i nouveau > /dev/null
    if [ $? = "0" ]; then
        return 1
    fi
    return 0
}

check_x_nvidia_device() {
    cat "${X_CONF}" | egrep -i '^[^#]*Driver[[:space:]]+"{0,1}nvidia"{0,1}'
}

check_if_installed() {
    local _package_name=$1

    if [[ ${DCV_SYSTEM_TYPE} =~ "ubuntu" ]] || [[ ${DCV_SYSTEM_TYPE} =~ "debian" ]]; then
        dpkg -l ${_package_name} | grep "^ii" >> "${LOG_FILE}" 2>&1
    elif [[ ${DCV_SYSTEM_TYPE} =~ "rhel" ]] || [[ ${DCV_SYSTEM_TYPE} =~ "sles" ]] || [[ ${DCV_SYSTEM_TYPE} =~ "amzn" ]]; then
        rpm -q ${_package_name} >> "${LOG_FILE}" 2>&1
    else
        log "Unknown system '$DCV_SYSTEM_TYPE', failed to check if package '$_package_name' is installed"
        return 1
    fi

    return $?
}

check_x_required_packages() {
    local _dcv_system_type_name=${1//_*}
    declare -a _xorg_packages

    log "Checking X Required packages..."
    if [ "${_dcv_system_type_name}" = "rhel" ]; then
        _xorg_packages=(xorg-x11-server-Xorg xorg-x11-server-utils xorg-x11-utils)
    elif [ "${_dcv_system_type_name}" = "sles" ]; then
        _xorg_packages=(xorg-x11-server)
    elif [ "${_dcv_system_type_name}" = "ubuntu" ]; then
        _xorg_packages=(xorg)
    fi

    for _package in "${_xorg_packages[@]}"; do
        if ! check_if_installed $_package; then
            return 1
        fi
    done
    log "X Required packages are installed."
}

check_x_busids() {
  local res=0
  local _non_nvidia_cards=$(lspci | egrep -i vga | egrep -i -v NVIDIA | wc -l)
  if [ "${_non_nvidia_cards}" != "0" ]; then
    local _nvidia_configured_cards=$(nvidia-xconfig -t | egrep '^([ ]+[|]){3}--> Driver "nvidia"' | wc -l)
    local _busids=$(nvidia-xconfig -t | egrep '^([ ]+[|]){3}--> BusID' | wc -l)
    if [ "${_nvidia_configured_cards}" != "${_busids}" ]; then
      res=1
    fi
  fi
  return "${res}"
}

check_potential_gpu_fps_drop () {
  local NVIDIA_HARDDPMS="440.0"
  cmp_versions ${NVIDIA_HARDDPMS} $1
  if [ "$?" == "1" ]; then
    cat "$X_CONF" | egrep -i "^[^#]*Option[[:space:]]+\"{0,1}HardDPMS\"{0,1}\"[[:space:]]+\"{0,1}false\"{0,1}" 2>&1 > /dev/null
    if [ "$?" != "0" ]; then
      return 1
    fi
  fi
  return 0
}

file_readable() {
    if [ -f "$1" -a -r "$1" ] ; then
        return 0
    else
        return 1
    fi
}

files_exists() {
    for f in $*; do
        printf "File ${f}: "
        if [ ! -f "$f" ] ; then
            printf "NOT FOUND\n"
            return 1
        fi
        printf "FOUND\n"
    done

    return 1
}

has_xaccess() {
    for d in $*; do
        DISPLAY=${d} xhost 2>/dev/null | grep "^LOCAL:$" >/dev/null || return 1
    done
    return 0
}

has_nvidia() {
    for d in $*; do
        DISPLAY=${d} glxinfo 2>/dev/null | grep "^[[:space:]]*OpenGL vendor string: NVIDIA Corporation$" >/dev/null
        if [ $? -ne 0 ]; then
            return 1
        fi
    done
    return 0
}

has_glx() {
    for d in $*; do
        DISPLAY=${d} xdpyinfo 2>/dev/null | grep "^[[:space:]]*GLX$" >/dev/null
        if [ $? -ne 0 ]; then
            return 1
        fi
    done
    return 0
}

has_depth24() {
    for d in $*; do
        _visuals=$(DISPLAY="${d}" glxinfo 2>/dev/null | egrep "^0x[0-9a-f]+ 24 " | wc -l)
        log "Found ${_visuals} visuals supporting 24bpp on display ${d}."
        if [ "${_visuals}" = "0" ]; then
            return 1
        fi
    done
    return 0
}

use_lspci_with_grep() {
    which lspci >/dev/null 2>/dev/null
    _lspci_available="$?"

    if [ "${_lspci_available}" = 0 ]; then
        lspci | grep "$1"
    else
        echo 'Missing LSPCI, cannot check ...'
    fi
}

check_dev_files_permissions() {
    stat -t /dev/nvidia* >/dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo "NVIDIA device files are missing"
        return 1
    fi
    for f in /dev/nvidia*; do
        # Skip nvidia-caps, introduced with CUDA 11/R450
        # https://docs.nvidia.com/datacenter/tesla/mig-user-guide/
        if [ "$f" == "/dev/nvidia-caps" ]; then
            continue
        fi
        local perms=`stat --format=%a "${f}" 2>/dev/null`
        echo "File: ${f} - Permissions: ${perms}"
        if [ "${perms}" != "666" ]; then
          return 1
        fi
    done
}

check_opengl_libs() {
    local dcvgladmin_enabled=1
    local dcvgladmin_status_output=""

    which dcvgladmin 2> /dev/null > /dev/null
    if [ "$?" = "0" ]; then
        if [ "$1" = "x86_64" ]; then
            dcvgladmin_arch_check="64"
        else
            dcvgladmin_arch_check="32"
        fi

        dcvgladmin_status_output=$(dcvgladmin status "${dcvgladmin_arch_check}")
        dcvgladmin_enabled="$?"
        enable_legacy=""
        if [ "${dcvgladmin_enabled}" = "0" ]; then
            echo $dcvgladmin_status_output | grep "GLVND" > /dev/null
            if [ $? -ne 0 ]; then
                enable_legacy="--noglvnd"
            fi
            # disable dcvgladmin before making the nvidia checks
            dcvgladmin disable "${dcvgladmin_arch_check}" 2> /dev/null > /dev/null
        fi
    fi

    local res=1
    local libs=
    if [ "$1" = "x86_64" ]; then
        local usr_lib64=$(ldconfig -p | awk '($1 == "libGL.so.1" && $2 ~ /x86-64/) { print $4 }' | head -1)

        libs=$(ldd "${usr_lib64}" 2>/dev/null | grep -i nvidia)
        if [ "$?" = "0" ]; then
            echo "NVIDIA OpenGL 64-bit library found:\n${libs}"
            res=0
        else
            ldd "${usr_lib64}" 2>/dev/null | grep -i libGLdispatch.so > /dev/null
            if [ "$?" = "0" ]; then
                ldconfig -p | grep x86-64 | grep libGLX_nvidia.so.0 > /dev/null
                if [ "$?" = "0" ]; then
                    echo "NVIDIA OpenGL 64-bit library dispatcher found"
                    res=0
                else
                    echo "NVIDIA OpenGL 64-bit library dispatcher NOT found."
                    res=1
                fi
            else
                echo "NVIDIA OpenGL 64-bit library NOT found."
                res=1
            fi
        fi
    else
        local usr_lib=$(ldconfig -p | awk '($1 == "libGL.so.1" && $2 ~ /\(libc6\)/) { print $4 }' | head -1)

        libs=$(ldd "${usr_lib}" 2>/dev/null | grep -i nvidia)
        if [ "$?" = "0" ]; then
            echo "NVIDIA OpenGL 32-bit library found:\n${libs}."
            res=0
        else
            ldd "${usr_lib}" 2>/dev/null | grep -i libGLdispatch.so > /dev/null
            if [ "$?" = "0" ]; then
                ldconfig -p | grep \(libc6\) | grep libGLX_nvidia.so.0 > /dev/null
                if [ "$?" = "0" ]; then
                    echo "NVIDIA OpenGL 32-bit library dispatcher found"
                    res=0
                else
                    echo "NVIDIA OpenGL 32-bit library dispatcher NOT found."
                    res=1
                fi
            else
                echo "NVIDIA OpenGL 32-bit library NOT found."
                res=1
            fi
        fi
    fi

    # re-enable dcvgladmin if needed
    if [ "${dcvgladmin_enabled}" = "0" ]; then
        dcvgladmin enable "${dcvgladmin_arch_check}" "$enable_legacy" 2> /dev/null > /dev/null
    fi

    return "${res}"
}

find_x_pid() {
    local dpynum=${1-0}
    local xpid

    # try with lsof if available
    which lsof > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        xpid=$(lsof -F '' /tmp/.X11-unix/X${dpynum} 2>/dev/null | head -1 | sed 's/^p//')
    fi

    [ -n "${xpid}" ] || xpid=$(ps -ef | egrep "Xorg[ ]*\:${dpynum}" | awk '{print $2}')
    [ -n "${xpid}" ] || xpid=$(ps -ef | egrep "X[ ]*\:${dpynum}" | awk '{print $2}')

    echo "${xpid}"
}

start_temporary_x() {
    X :0 -nolisten tcp vt$(fgconsole) 2>/dev/null &
    xpid=$!

    sleep 3

    ps "${xpid}" >/dev/null 2>&1
    local _xpid_found=$?
    if [ ${_xpid_found} -ne 0 ]; then
        log
        log ">>> Failed to start temporary X server <<<"
        log
        return ${_xpid_found}
    fi

    log
    log ">>> Started temporary X server [PID: ${xpid}] <<<"
    log

    echo "${xpid}"
}

stop_temporary_x() {
    local xpid=$1
    if [ -n ${xpid} ]; then
        ps ${xpid} >/dev/null 2>&1
        if [ $? -eq 0 ]; then
            kill "${xpid}"
            log
            log ">>> Stopped temporary X server <<<"
            log
        fi
    fi
}

ERRORS_FILE=$(mktemp dcvgldiag-errors-XXXXXX 2> /dev/null)
WARNINGS_FILE=$(mktemp dcvgldiag-warnings-XXXXXX 2> /dev/null)
INFO_FILE=$(mktemp dcvgldiag-info-XXXXXX 2> /dev/null)

# Check if dcvgldiag is running on a read-only file system
if [ $? -eq 1 ]; then
    echo "ERROR: dcvgldiag can't run on a read-only file system. Exiting ..."
    exit 1
fi

ERRORS=0
WARNINGS=0
INFO=0

if [ -z "${LOG_FILE}" ]; then
    # Try to create the log file in current directory
    LOG_FILE=$(mktemp --tmpdir="$PWD" dcvgldiag-XXXXXX 2> /dev/null)
else
    # Try to reset the log file
    echo "" 2>/dev/null > "${LOG_FILE}"
    if [ $? -ne 0 ]; then
      # If the specified log file cannot be created => fallback
      LOG_FILE=
    fi
fi

if [ -z "${LOG_FILE}" ]; then
    # Fallback case: write the log file in $TMPDIR
    LOG_FILE=$(mktemp dcvgldiag-XXXXXX)
fi

log() {
    echo -e "$1" >> "${LOG_FILE}"
}

echo_and_log() {
    echo -e "$1"
    log "$1"
}

echo_and_log_i() {
    if [ -z "${INSTALLER_MODE}" ]; then
        echo_and_log "$1"
    else
        log "$1"
    fi
}

die() {
    echo_and_log "$1"
    echo

    rm "${ERRORS_FILE}" 2> /dev/null
    rm "${WARNINGS_FILE}" 2> /dev/null
    rm "${INFO_FILE}" 2> /dev/null

    exit 1
}

print_summary() {
    if [ ${ERRORS} -gt 0 ]; then
        if [ ${ERRORS} -eq 1 ]; then
            printf "There is ${ERRORS} error"
        else
            printf "There are ${ERRORS} errors"
        fi
        if [ ${WARNINGS} -gt 0 ]; then
            if [ ${WARNINGS} -eq 1 ]; then
                printf " and ${WARNINGS} warning"
            else
                printf " and ${WARNINGS} warnings"
            fi
        fi
        if [ ${INFO} -gt 0 ]; then
            if [ ${INFO} -eq 1 ]; then
                printf " and ${INFO} info message"
            else
                printf " and ${INFO} info messages"
            fi
        fi
        printf ".\n\n"
    elif [ ${WARNINGS} -gt 0 ]; then
        if [ ${WARNINGS} -eq 1 ]; then
            printf "There is ${WARNINGS} warning"
        else
            printf "There are ${WARNINGS} warnings"
        fi
        if [ ${INFO} -gt 0 ]; then
            if [ ${INFO} -eq 1 ]; then
                printf " and ${INFO} info message"
            else
                printf " and ${INFO} info messages"
            fi
        fi
        printf ".\n\n"
    else
        if [ ${INFO} -gt 0 ]; then
            if [ ${INFO} -eq 1 ]; then
                printf "There is ${INFO} info message"
            else
                printf "There are ${INFO} info messages"
            fi
            printf ".\n\n"
        fi
        printf "No problem found.\n\n"
    fi
}

finish_tests() {
    printf " DONE\n"
    echo
    # TODO replace place holders
    cat "${ERRORS_FILE}" | sed "s/%NUM_ERRORS%/${ERRORS}/g"
    cat "${WARNINGS_FILE}" | sed "s/%NUM_WARNINGS%/${WARNINGS}/g"
    cat "${INFO_FILE}" | sed "s/%NUM_INFO%/${INFO}/g"

    rm "${ERRORS_FILE}" 2> /dev/null
    rm "${WARNINGS_FILE}" 2> /dev/null
    rm "${INFO_FILE}" 2> /dev/null

    echo
    print_summary
    if [ -z "${INSTALLER_MODE}" ]; then
        echo "A detailed report about the tests is available in '${LOG_FILE}'"
    else
        echo "A detailed report about the tests is available in the installation log."
    fi
    echo
    if [ ${ERRORS} -gt 0 ]; then
        exit 4
    fi
    if [ ${WARNINGS} -gt 0 ]; then
        exit 3
    fi
    exit 0
}


_dcv_version=$(dcv version 2> /dev/null | grep "^Amazon\|^NICE")
if [ -z "${_dcv_version}" ]; then
    die "ERROR: Unable to get the DCV version. Exiting..."
fi

SYSVERSION=$(get_system_pretty_version)

RUNLEVEL=$(/sbin/runlevel | cut -d " " -f 2)

which nvidia-smi 2> /dev/null > /dev/null
if [ "$?" = "0" ]; then
    NVIDIA_GPU=$(nvidia-smi -a | awk -F ': ' '/^GPU/{getline;print $2","}' | xargs | sed 's/.$//')
    NVIDIA_DRIVER=$(nvidia-smi -a | grep "Driver Version" | awk -F ': ' '{print $2}')
fi

echo
echo_and_log_i "${_dcv_version} - Diagnostic Script"
echo_and_log_i "============================================"

echo_and_log_i
echo_and_log_i "Date:             `date -R`"
echo_and_log_i
echo_and_log_i "Host:             `hostname`"
echo_and_log_i "Architecture:     `uname -i`"
echo_and_log_i "Operating System: ${SYSVERSION:-unknown}"
echo_and_log_i "Kernel Version:   `uname -r`"
echo_and_log_i "Nvidia GPU:       ${NVIDIA_GPU:-unknown}"
echo_and_log_i "Nvidia Driver:    ${NVIDIA_DRIVER:-unknown}"
echo_and_log_i "Runlevel:         $RUNLEVEL"
echo_and_log_i
echo_and_log_i "X configuration file: ${X_CONF}"
echo_and_log_i

# check user
_user_id=$(id -u)

if [ "${_user_id}" != "0" ] ; then
    die "ERROR: You need to be 'root' to execute the DCV GL Diagnostic Script. Exiting..."
fi

# Test all the pre-requisites to run the tests are satisfied

# Detect the DCV_SYSTEM_TYPE here. We cannot rely on the test done afterwards since it is in a forked shell
check_system >/dev/null 2>/dev/null

# Check basic diagnostic commands
which lspci >/dev/null 2>/dev/null || die "ERROR: Cannot find required diagnostic command 'lspci'. Exiting..."
which lsmod >/dev/null 2>/dev/null || die "ERROR: Cannot find required diagnostic command 'lsmod'. Exiting..."

# Are DCV packages installed?
for package in nice-dcv-server nice-xdcv nice-dcv-gl; do
    log "Cheking if $package package is installed..."
    if ! check_if_installed $package; then
        die "ERROR: $package is not installed on this host. Exiting..."
    fi
done

_res=$(which dcv 2> /dev/null)
if [ $? -ne 0 ]; then
    die "ERROR: the command 'dcv' is not in the PATH. Exiting..."
fi
log "The 'dcv' command is located at: ${_res}"

_res=$(which dcvgladmin 2> /dev/null)
if [ $? -ne 0 ]; then
    die "ERROR: the command 'dcvgladmin' is not in the PATH. Exiting..."
fi
log "The 'dcvgladmin' command is located at: ${_res}"

if [ ! -r "/etc/dcv/dcv.conf" ]; then
    die "ERROR: cannot find the DCV configuration file. Exiting..."
fi
log
log "The DCV configuration file is readable."
log

if [ ! -r "/etc/dcv/dcv-gl.conf" ]; then
    die "ERROR: cannot find the DCV-GL configuration file. Exiting..."
fi
log
log "The DCV-GL configuration file is readable."
log

echo_and_log_i "`dcvgladmin status | grep ^DCV`"
log

# get local display configured in dcv-gl.conf or fallback to :0.0
_local_display_list="${DCV_GL_DISPLAY}"
if [ -z "${_local_display_list}" ]; then
    log "The DCV_GL_DISPLAY environment variable is not set, reading the 'dcv.conf' file"
    _local_display_list=$(cat /etc/dcv/dcv.conf | sed -n -e 's/^[ ]*gl-displays[ ]*=[ ]*\(.*\)[ ]*$/\1/p' | head -1 | sed "s/[][' ]//g" | sed "s/,/ /g")
fi

if [ -z "${_local_display_list}" ]; then
    log "The 'gl-displays' option is not specified in the 'dcv.conf' file, falling back to ':0.0'"
    _local_display_list=":0.0"

    # FIXME: log this as a WARNING?
fi

echo_and_log_i

if [ -z ${INSTALLER_MODE} ]; then
    printf "Running tests: "
else
    printf "Running post-installation tests: "
fi

# System checks
supported_systems="
  - Red Hat Enterprise Linux 8.4 or later
  - Red Hat Enterprise Linux 9.0 or later
  - CentOS Stream 9
  - Debian 12
  - Rocky Linux 8.10 or later
  - Rocky Linux 9.4 or later
  - Amazon Linux 2
  - Amazon Linux 2023
  - SUSE Linux Enterprise Server 12 SP5 or later
  - SUSE Linux Enterprise Server 15 SP2 or later
  - Ubuntu 20.04
  - Ubuntu 22.04
  - Ubuntu 24.04
"
check "Check if the Operating System is supported" \
      "DCV supports only the following operating systems: \n$supported_systems"\
      "check_system" \
      "E" \
      "The installed Operating system is not supported." \
      "Please, install DCV on a supported operating system."

check "Basic network configuration" \
      "Check if the host name is correctly resolved and the associated IP can be reached." \
      "ping -c 1 $(hostname) 2>/dev/null | grep '1 received'" \
      "E" \
      "Cannot ping '$(hostname)'." \
      "Please, check you network configuration."

# X configuration checks

check "X Window System required packages are installed" \
      "Check if the required packages for the X Windows system are installed" \
      "check_x_required_packages $DCV_SYSTEM_TYPE" \
      "E" \
      "X Window System required packages are NOT installed." \
      "Please, check the X Window System installation. Check at least the following packages are installed:
              On Red Hat:
                xorg-x11-server-Xorg, xorg-x11-server-utils, xorg-x11-utils
              On SuSE:
                xorg-x11-server, xorg-x11, freeglut
              On Ubuntu:
                xorg"

if [ "$?" == "0" ]; then
    check "The 3D X Server has a configuration file." \
          "Check if the '$X_CONF' file is available." \
          "[ -r \"${X_CONF}\" ]" \
          "E" \
          "The '$X_CONF' file is missing or not readable." \
          "Please, check the X Server configuration."
    if [ "$?" == "0" ]; then
        check "The 3D X Server is configured to use the NVIDIA driver" \
              "Check if in the '$X_CONF' file there is at least one device using the NVIDIA driver." \
              "check_x_nvidia_device" \
              "E" \
              "There are no devices in '$X_CONF' configured to use the NVIDIA driver." \
              "Please, check the X Server configuration."

        for display in ${_local_display_list}; do
            DISPLAY=$display xrandr 2>/dev/null | grep -i ".*connected.*primary.*" 2>&1 > /dev/null
            if [ "$?" != "0" ]; then
                check "X server configuration when no monitor is connected" \
                      "Check if the X server is appropriately configured in case there is no monitor connected to the video card adapter." \
                      'cat "$X_CONF" | egrep -i '"'"'^[^#]*Option[[:space:]]+"{0,1}UseDisplayDevice"{0,1}"[[:space:]]+"{0,1}none"{0,1}'"'"'' \
                      "W" \
                      "The X server configuration does not contain the \"UseDisplayDevice\" \"none\" option. Ignore this warning if a monitor is connected to the video card adapter." \
                      "In case there is no monitor connected to the video card adapter associated to DISPLAY=$display, you may need to add the \"UseDisplayDevice\" \"none\" option to the appropriate \"Device\" section (of the '$X_CONF' file."
            fi
        done

        if [ "${_nvidia_available}" == "0" ]; then
            check "Xorg PCI BusID configuration" \
                  "Check if the X server is appropriately configured in case there are multiple video adapters." \
                  'check_x_busids' \
                  "E" \
                  "The X server configuration does not contain the 'BusID' parameter for all NVIDIA Devices." \
                  "Please check the '$X_CONF' file and add the right PCI Bus ID corresponding to the NVIDIA graphic cards.

              The PCI Bus ID can be found using:
                nvidia-xconfig --query-gpu-info

              A new xorg.conf can be generated with:
                nvidia-xconfig --busid=<PCI Bus ID>

              In case of multiple NVIDIA GPUs use:
                nvidia-xconfig -a --device=Device0 --busid=<PCI Bus ID of GPU #0>
                                  --device=Device1 --busid=<PCI Bus ID of GPU #1>
                                  ..."
        fi
    fi
fi

# X checks (require an X server running), check if we have one or try to start it
_display_list=$(echo "${_local_display_list}" | tr " " "\n" | sed -n '/^:/{s/^:\([0-9][0-9]*\).*$/\1/;p;}' | sort -u)
for _display_num in ${_display_list}; do
    _xpid=$(find_x_pid ${_display_num})
    if [ "$RUNLEVEL" == "5" ]; then
        check "3D X Server [:${_display_num}] is running in runlevel 5" \
          "Check if the 3D X Server [:${_display_num}] is running in runlevel 5" \
          "[ -n \"${_xpid}\" ]" \
          "E" \
          "Cannot find a running 3D X Server [:${_display_num}]" \
          "Please, make sure the X process is started. Try switching to runlevel 3 and back to runlevel 5."
    fi
    _temp_x=""
    if [ -n "${_xpid}" ]; then
        log "Found X for display :${_display_num} [PID: ${_xpid}]"
        log
    else
        log "Starting a temporary X for display :${_display_num}"
        read _temp_x < <(start_temporary_x)
        check "3D X Server [:${_display_num}] is able to start" \
          "Check if the 3D X Server [:${_display_num}] is able to start" \
          "[ -n \"${_temp_x}\" ]" \
          "E" \
          "Cannot start a temporary 3D X Server on display :${_display_num}" \
          "Please, check the X logs for display ${_display_num}."
    fi
done

# Current VT
_current_vt="vt$(fgconsole)"

if [ -n "${_xpid}" ]; then
    check "Access to the 3D X Server [${_local_display_list}]" \
          "Check if applications running with DCV are able to access the 3D X Server [${_local_display_list}]" \
          "has_xaccess ${_local_display_list}" \
          "E" \
          "DCV cannot access the 3D X Server [${_local_display_list}]." \
          "Please, stop the 3D X Server and disable DCV with 'dcvgladmin disable'.\nThen enable DCV with 'dcvgladmin enable' and restart the 3D X Server."

    check "GLX support in 3D X Server [${_local_display_list}]" \
          "Check the GLX extension is available for the 3D X Server '${_local_display_list}" \
          "has_glx ${_local_display_list}" \
          "E" \
          "The 3D X Server [${_local_display_list}] does not support the GLX extension" \
          "Please, check your X server configuration and restart it."

    check "3D X Server [${_local_display_list}] color depth" \
          "Check the 3D X Server [${_local_display_list}] run with depth 24." \
          "has_depth24 ${_local_display_list}" \
          "E" \
          "The 3D X Server [${_local_display_list}] does not run with depth 24" \
          "Please, check your X server configuration and restart it."

    _local_display_vt=$(cat /proc/${_xpid}/cmdline | strings -1 | grep ^vt)
    if [ -n "${_local_display_vt}" ]; then
        check "3D X Server [${_local_display_list}] virtual terminal" \
              "Check the 3D X Server [${_local_display_list}] is running on the active VT." \
              "test ${_current_vt} = ${_local_display_vt}" \
              "E" \
              "The 3D X Server [${_local_display_list}] is running on '${_local_display_vt}', while the current VT is '${_current_vt}'" \
              "Please, use 'chvt' to change the current VT to match the 3D X Server."
    else
        log "Unable to find X server VT. Skipping VT check"
    fi
fi

# NVIDIA checks
check "NVIDIA card is installed" \
      "Check if a NVIDIA card is installed by trying to execute the 'lspci' command and parsing the output." \
      "lspci | grep -i nvidia >/dev/null 2>/dev/null" \
      "W" \
      "No NVIDIA card found" \
      "Please, check a NVIDIA card is installed."

_has_nvidia=$?

if [ "${_has_nvidia}" = "0" ]; then
    if [ "${DCV_SYSTEM_TYPE}" = "rhel_6" ]; then
        check "Nouveau kernel module is not loaded" \
              "Check if in the nouveau kernel module is loaded." \
              "check_nouveau" \
              "E" \
              "Detected nouveau kernel module." \
              "The NVIDIA driver installer did not manage to disable the 'nouveau' driver.

               This is usually due to the fact that the nouveau driver is loaded at the start of
               your system. You can fix this problem by following these steps:

               1: add the following line to the blackisted module configuration in '/etc/modprobe.d/blacklist.conf': blacklist nouveau
               2: (required on some distributions) add the following parameters to the kernel command line in 'grub.conf':
                  rdblacklist=nouveau
                  nouveau.modeset=0
               3: reboot

               Please consult the 'Common Problems' section of the NVIDIA drivers documentation
               for more information about this issue."
    elif [ "${DCV_SYSTEM_TYPE}" = "rhel_7" -o "${DCV_SYSTEM_TYPE}" = "rhel_8" ]; then
        check "Nouveau kernel module is not loaded" \
              "Check if in the nouveau kernel module is loaded." \
              "check_nouveau" \
              "E" \
              "Detected nouveau kernel module." \
              "The NVIDIA driver installer did not manage to disable the 'nouveau' driver.

               This is usually due to the fact that the nouveau driver is loaded at the start of
               your system. You can fix this problem by following these steps:

               1: open '/etc/default/grub' and add this at the end of 'GRUB_CMDLINE_LINUX' line: rdblacklist=nouveau
               2: update grub: grub2-mkconfig -o /boot/grub2/grub.cfg
               3: open '/etc/modprobe.d/blacklist.conf' (or create it, if no one) and add this line: blacklist nouveau
               4: reboot

               Please consult the 'Common Problems' section of the NVIDIA drivers documentation
               for more information about this issue."
    else
        check "Nouveau kernel module is not loaded" \
              "Check if in the nouveau kernel module is loaded." \
              "check_nouveau" \
              "E" \
              "Detected nouveau kernel module." \
              "The NVIDIA driver installer did not manage to disable the 'nouveau' driver.

               This is usually due to the fact that the nouveau driver is loaded at the start of
               your system. Disable nouveau driver and reboot.

               Please consult the 'Common Problems' section of the NVIDIA drivers documentation
               for more information about this issue."
    fi

    _nvidia_available=1
    if [ "$?" == "0" ]; then
        check "NVIDIA driver is installed" \
              "Check if the NVIDIA driver is installed by trying to execute the 'nvidia-smi' command and parsing the output." \
              "nvidia-smi -a 2>&1 | grep 'Driver Version'" \
              "E" \
              "Error executing nvidia-smi. There could be a problem in the NVIDIA card or the NVIDIA driver." \
              "Please, check the NVIDIA card and driver installation."
        _nvidia_available=$?

        if [ "${_nvidia_available}" == "0" ]; then
            installed_version=$(nvidia-smi -a | grep 'Driver Version' | cut -d ':' -f 2 | tr -d ' ')
            if  [ -n "${installed_version}" ]; then
                check "NVIDIA driver version" \
                      "Check if the NVIDIA driver version is ${NVIDIA_MIN_VERSION} or newer" \
                      "cmp_versions \"${installed_version}\" \"${NVIDIA_MIN_VERSION}\"" \
                      "E" \
                      "The installed NVIDIA driver (version ${installed_version}) is too old. Version ${NVIDIA_MIN_VERSION} or later is required." \
                      "Please, update the NVIDIA driver to version ${NVIDIA_MIN_VERSION} or later."
            fi

            check "NVIDIA kernel module is loaded" \
                  "Check if the NVIDIA kernel module is correctly loaded by the Linux kernel." \
                  "lsmod 2>&1 | grep -i ^nvidia" \
                  "E" \
                  "The NVIDIA kernel module is not loaded." \
                  "Please, check the NVIDIA drivers installation. In case you upgraded the Kernel, you must reinstall the NVIDIA drivers."

            check "NVIDIA device file permissions" \
                  "Check if the NVIDIA device files have the right permissions." \
                  "check_dev_files_permissions" \
                  "W" \
                  "At least one of the '/dev/nvidia*' device files do not have 666 access rights." \
                  "Modify the configuration file of the nvidia kernel module (usually /etc/modprobe.d/50-nvidia.conf or /etc/modprobe.d/50-nvidia-default.conf) and ensure that it contains NVreg_DeviceFileMode=0666 and NVreg_ModifyDeviceFiles=1. You may also need to change permissions in the 'install nvidia' line of the same file from 660 to 666. Restart the system after changing the file."

            check "OpenGL 64-bit libraries are installed" \
                  "Check if the NVIDIA OpenGL 64-bit libraries are installed" \
                  "check_opengl_libs x86_64" \
                  "E" \
                  "Cannot find NVIDIA OpenGL 64-bit libraries." \
                  "Please, check the NVIDIA driver installation."

            check "GPU frame rate could drop to 1 FPS" \
                  "Check if the GPU frame rate could drop to 1 FPS" \
                  "check_potential_gpu_fps_drop \"${installed_version}\"" \
                  "I" \
                  "The X server configuration does not contain the \"HardDPMS\" \"false\" option. Ignore this message if you are not experiencing GPU frame rate drops." \
                  "Otherwise, if you are experiencing GPU frame rate drops, add the \"HardDPMS\" \"false\" option to the appropriate \"Device\" section of the '$X_CONF' file.
  A restart of the X server is needed in order to apply the option.
  IMPORTANT: the restart of the X server can trigger a crash to all OpenGL applications running.
  Therefore it is strongly recommended to save your work and close each running virtual session before."

            if [ -n "${_xpid}" ]; then
                check "The 3D X Server [${_local_display_list}] is using the NVIDIA driver" \
                      "Check if the 3D X Server is using the NVIDIA driver" \
                      "has_nvidia ${_local_display_list}" \
                      "E" \
                      "The 3D X Server [${_local_display_list}] is not using the NVIDIA driver" \
                      "Please, check the 3D X Server is correctly setup to use the NVIDIA driver"
            fi

            _gpu_count=$(nvidia-smi -L | wc -l)
            _default_cuda_devices=""
            for i in $(seq 0 $((_gpu_count-1))); do
                if [ "$(nvidia-smi -i $i -a | grep "Compute Mode" | cut -d':' -f2 | tr -d ' ')" == 'Default' ]; then
                    _default_cuda_devices+="$i "
                fi
            done

            _dcv_cuda_devices=$(cat /etc/dcv/dcv.conf | grep ^cuda-devices | cut -d'=' -f2 | tr "[:punct:]" " ")
            # pick all devices if no one was specified
            if [ -z "$_dcv_cuda_devices" ]; then
                _dcv_cuda_devices=$(seq 0 $((_gpu_count-1)))
            fi

            _dcv_default_cuda_devices=""
            _dcv_no_default_cuda_devices=""
            sep1=""
            sep2=""
            for d in $_dcv_cuda_devices; do
                if [[ " $_default_cuda_devices " =~ " $d " ]]; then
                    _dcv_default_cuda_devices+="$sep1'$d'"
                    sep1=", "
                else
                    _dcv_no_default_cuda_devices+="$sep2'$d'"
                    sep2=", "
                fi
            done

            if [ -n "$_dcv_default_cuda_devices" ]; then
                _cuda_devices_configuration_message="Alternatively, if you want to keep this configuration,
  remove them from the DCV CUDA devices list adding or modifing the following parameter in /etc/dcv/dcv.conf

  [display]
  cuda-devices=[$_dcv_default_cuda_devices]"
            fi

            check "Cuda devices are properly configured" \
                  "Check if cuda devices used by DCV are using \"Default\" as compute mode." \
                  '[ -z "${_dcv_no_default_cuda_devices}" ]' \
                  "W" \
                  "The NVIDIA devices with id ${_dcv_no_default_cuda_devices} are configured with a compute mode that is not compatible with DCV." \
                  "Please, switch them to compute mode \"Default\" running the following command for each one:

  nvidia -c 0 -i <device id>

  $_cuda_devices_configuration_message"
        fi
    fi
fi

if [ -n "${_temp_x}" ]; then
    stop_temporary_x "${_temp_x}"
fi

finish_tests

# TODO
# Check dcv-gl.conf file permissions (readable by world)
# Check minimal desktop (TWM or better) is installed

# --------------------------------------------------------------------------- #
# ex: set ts=4 sw=4 et syntax=sh :
# --------------------------------------------------------------------------- #
