#!/usr/bin/env bash

################################################################################
# Copyright (C) 2021-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.
################################################################################

set -u
set -e # Abort in case a command fails. Where we want to handle failures manually, we append "&& :"

declare -r -i DEBUG_ENABLE_TRACING=0

if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
    set -x
fi

# Create temporary log file
readonly USERNAME=$(whoami)
readonly TMP_SESSION_LOG="/tmp/dcv-xsession.$USERNAME.tmp$$.log"
exec >"$TMP_SESSION_LOG" 2>&1

# Load environment from stdin
source <(cat -)

# Configure substitutions
readonly prefix=/usr
readonly exec_prefix=/usr
readonly bindir=/usr/bin
readonly datadir=/usr/share
readonly libdir=/usr/lib/aarch64-linux-gnu
readonly libexecdir=/usr/lib/aarch64-linux-gnu
readonly sysconfdir=/etc

# Buildtime constants
readonly XDCV_BIN="${bindir}/Xdcv"
if [[ ! ${DCV_AGENT_DIR-} ]]; then
    DCV_AGENT_DIR="${libexecdir}/dcv"
fi
readonly DCV_AGENT_DIR
readonly DCV_AGENT_BIN="$DCV_AGENT_DIR/dcvagent"
readonly DEFAULT_SESSION_INIT_SCRIPT="${sysconfdir}/dcv/dcvsessioninit"
readonly DEFAULT_SETTINGS_PATH="${sysconfdir}/dcv/dcv.conf"
readonly AGENT_DCV_MODULE_DIR="${libdir}/dcv/modules"
readonly AGENT_GIO_EXTRA_MODULES=$(echo "${libdir}/dcv/gio/modules:${GIO_EXTRA_MODULES-}" | sed 's/:$//g')
readonly AGENT_LD_LIBRARY_PATH=$(echo "${libdir}/dcv:${LD_LIBRARY_PATH-}" | sed 's/:$//g')
readonly AGENT_XDG_DATA_DIRS=$(echo "${datadir}:${XDG_DATA_DIRS-}" | sed 's/:$//g')
readonly AGENT_GST_PLUGIN_SCANNER="${libexecdir}/dcv/gst-plugin-scanner"
readonly AGENT_GST_PLUGIN_SYSTEM_PATH="${libdir}/dcv/gstreamer-1.0"
readonly BASH_MAJOR=${BASH_VERSINFO[0]}
readonly BASH_MINOR=${BASH_VERSINFO[1]}

# Defaults
readonly DEFAULT_LOG_LEVEL="info"
declare -r -i DEFAULT_LOG_ROTATE=10

# Log settings
if [[ ! ${DCV_STARTER_LOG_VERBOSITY-} ]]; then
    declare -r -i DCV_STARTER_LOG_VERBOSITY=3
fi
declare -r -i LOG_VERBOSITY=$DCV_STARTER_LOG_VERBOSITY # 0: silent, 1: error, 2: warning, 3:info, 4:debug
declare LOG_DOMAIN="session-starter"

# Constants
declare -r -i UNRECOVERABLE_ERROR=1
declare -r -i DISPLAY_READ_TIMEOUT=15 #secs
declare -r -i DCV_AGENT_STATUS_UNRECOVERABLE_ERROR=6 # see agentapplication.h

# Global variables
declare XDCV_ADDITIONAL_ARGS=""
declare -i XDCV_PID=0
declare -i XINIT_PID=0
declare -i XINIT_SID=0
declare -i AGENT_PID=0
declare -i EXITING=0
declare -i USE_CHILDREN_WATCHDOG=1

declare -x DCV_VIRTUAL_SESSION=1

error() { log 1 "ERROR" "$1"; }
warn() { log 2 "WARN " "$1"; }
message() { log 3 "INFO " "$1"; }
debug() { log 4 "DEBUG" "$1"; }
log() {
    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set +x
    fi

    local -r -i verbosity=$1
    local -r level="$2"
    local -r msg="$3"

    if [ $LOG_VERBOSITY -ge "$verbosity" ]; then
         datestring=$(date -u +'%Y-%m-%d %H:%M:%S,%N')
         echo -e "$datestring [$$] $level $LOG_DOMAIN - $msg"
    fi

    if [[ $DEBUG_ENABLE_TRACING -ne 0 ]]; then
        set -x
    fi
}

check_mandatory_env_vars() {
    if [[ ! ${XDG_SESSION_TYPE-} ]]; then
        error "Missing mandatory environment variable XDG_SESSION_TYPE"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly XDG_SESSION_TYPE

    if [[ ! ${DCV_SESSION_ID-} ]]; then
        error "Missing mandatory environment variable DCV_SESSION_ID"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_SESSION_ID

    if [[ ! ${DCV_LOG_DIR-} ]]; then
        error "Missing mandatory environment variable DCV_LOG_DIR"
        die "$UNRECOVERABLE_ERROR"
    fi
    readonly DCV_LOG_DIR

    # This is not manadatory, but we need it before creating the session log
    if [[ ! ${DCV_LOG_MAX_FILE_ROTATIONS-} ]]; then
        debug "Use default value '$DEFAULT_LOG_ROTATE' for DCV_LOG_MAX_FILE_ROTATIONS"
        DCV_LOG_MAX_FILE_ROTATIONS="$DEFAULT_LOG_ROTATE"
    fi
    readonly DCV_LOG_MAX_FILE_ROTATIONS
}

# FIXME: check if files/directories are readable/executable/etc.
checks_optional_env_vars() {
    if [[ ! ${DCV_SESSION_INIT_SCRIPT-} ]]; then
        debug "Use default value '$DEFAULT_SESSION_INIT_SCRIPT' for DCV_SESSION_INIT_SCRIPT"
        DCV_SESSION_INIT_SCRIPT="$DEFAULT_SESSION_INIT_SCRIPT"
    fi
    readonly DCV_SESSION_INIT_SCRIPT

    if [[ ! ${DCV_SETTINGS_PATH-} ]]; then
        debug "Use default value '$DEFAULT_SETTINGS_PATH' for DCV_SETTINGS_PATH"
        DCV_SETTINGS_PATH="$DEFAULT_SETTINGS_PATH"
    fi
    readonly DCV_SETTINGS_PATH

    if [[ ! ${DCV_LOG_LEVEL-} ]]; then
        debug "Use default value '$DEFAULT_LOG_LEVEL' for DCV_LOG_LEVEL"
        DCV_LOG_LEVEL="$DEFAULT_LOG_LEVEL"
    fi
    readonly DCV_LOG_LEVEL

    if [[ ${DCV_XDCV_ADDITIONAL_ARGS-} ]]; then
        XDCV_ADDITIONAL_ARGS="$DCV_XDCV_ADDITIONAL_ARGS "
    fi

    [[ $XDCV_ADDITIONAL_ARGS =~ -logverbose\ +[0-9]+ ]] || XDCV_ADDITIONAL_ARGS+="-logverbose 3 "
    [[ $XDCV_ADDITIONAL_ARGS =~ -dpi\ +[0-9]+ ]] || XDCV_ADDITIONAL_ARGS+="-dpi 96 "
    [[ $XDCV_ADDITIONAL_ARGS =~ -(no)?listen\ +tcp ]] || XDCV_ADDITIONAL_ARGS+="-nolisten tcp "

    readonly XDCV_ADDITIONAL_ARGS
}

rotate_log() {
    local -r -i num=$1
    local -r fname="$2"

    debug "Rotate log file: $fname"

    for ((i = num - 1; i > 0 ; i--)); do
        if [ -r "$fname.$i" ]; then
            mv -f "$fname.$i" "$fname.$((i+1))" 2>/dev/null
        fi
    done

    if [ -r "$fname" ]; then
        if [[ $num -gt 0 ]]; then
            mv -f "$fname" "$fname.1"
        else
            rm -f "$fname"
        fi
    fi

    debug "Done"
}

check_process() {
    local -r -i pid=$1

    if [[ $pid -lt 1 ]]; then
        return 1
    fi

    kill -0 "$pid" 2> /dev/null && :
    if [[ $? -eq 0 ]]; then
        return 0
    else
        return 1
    fi
}

kill_process() {
    local -r -i pid=$1
    local -r pname="$2"

    if ! check_process "$pid"; then
        return
    fi

    message "Kill $pname [$pid]"
    kill -9 "$pid" && :
    if [[ $? -ne 0 ]]; then
        warn "Failed to kill $pname [$pid]"
    fi
    wait "$pid" && :
}

kill_xinit_session() {
    if [[ -z "$XINIT_SID" ]]; then
        warn "Init session ID unknown, unable to terminate processes"
        return
    fi

    if [[ -z "$(pgrep -s $XINIT_SID)" ]]; then
        message "No init process to kill in session '$XINIT_SID'"
        return
    fi

    message "Terminate init session '$XINIT_SID'"
    pkill -TERM -s "$XINIT_SID" && :
    if [[ $? -ne 0 ]]; then
        warn "Failed to term init session '$XINIT_SID'"
    fi

    # poll for processes termination in the session.
    # Wait 3 seconds using an exponential backoff
    # to limit the number of polling iteration.
    local -i deadline=$(($SECONDS + 3))
    for ((i=3; $SECONDS < $deadline; i=$((i*2)))); do
        message "Sleeping $i ms ..."
        sleep "$i.0e-3"
        if [[ -z "$(pgrep -s $XINIT_SID)" ]]; then
            message "All processes terminated"
            return;
        fi
    done

    message "Kill init session '$XINIT_SID'"
    pkill -9 -s "$XINIT_SID" && :
    if [[ $? -ne 0 ]]; then
        warn "Failed to kill init session '$XINIT_SID'"
    fi

    wait "$XINIT_PID" && :
}

cleanup_processes() {
    # FIXME: try with the TERM signal before killing everything
    kill_process "$AGENT_PID" "DCV agent" && :
    kill_xinit_session && :
    kill_process "$XDCV_PID" "Xdcv" && :
}

is_process_alive() {
    local -r -i pid=$1
    local -r pname="$2"

    if ! check_process "$pid"; then
        if wait "$pid"; then
            message "$pname [$pid] exited normally"
            return 1
        else
            warn "$pname [$pid] exited abnormally (returned $?)"
            return 2
        fi
    else
        return 0
    fi
}

die() {
    local -r -i ret=$1

    if [[ $EXITING -ne 0 ]]; then
        return
    fi

    if [[ $USE_CHILDREN_WATCHDOG -eq 0 ]]; then
        trap - CHLD
    fi

    EXITING=1

    # [Judges 16:30] Samson said, “Let me die with the Philistines!”
    cleanup_processes && :
    if [[ $XAUTHORITY ]]; then
        message "Delete Xauthority file: $XAUTHORITY"
        rm -f $XAUTHORITY
    fi
    message "Exiting (exit value: $ret)"
    exit "$ret"
}

launch_agent() {
    local -a cmd
    local -i idx=0

    # Prepare the command line to execute
    cmd[idx++]="$DCV_AGENT_BIN"
    cmd[idx++]="--mode" && cmd[idx++]="full"
    cmd[idx++]="--session-id" && cmd[idx++]="$DCV_SESSION_ID"

    cmd[idx++]="--display" && cmd[idx++]="$DISPLAY"
    cmd[idx++]="--settings-path" && cmd[idx++]="$DCV_SETTINGS_PATH"
    cmd[idx++]="--log-level" && cmd[idx++]="$DCV_LOG_LEVEL"
    cmd[idx++]="--log-dir" && cmd[idx++]="$DCV_LOG_DIR"
    [[ ${1-} ]] && cmd[idx++]="--log-rotate-at-startup"

    message "Launch DCV agent: ${cmd[*]}"
    if [ ! -x "$DCV_AGENT_BIN" ]; then
        error "Cannot launch DCV agent: '$DCV_AGENT_BIN' is not executable"
        die "$UNRECOVERABLE_ERROR"
    fi

    # Create execution env for the agent
    declare -x DCV_MODULE_DIR="$AGENT_DCV_MODULE_DIR"
    debug "DCV_MODULE_DIR=$DCV_MODULE_DIR"

    # force pci-bus-id ordering so we match nvidia-smi output
    declare -x CUDA_DEVICE_ORDER="PCI_BUS_ID"
    debug "CUDA_DEVICE_ORDER=$CUDA_DEVICE_ORDER"

    if [[ ${GIO_EXTRA_MODULES-} ]]; then
        local -r old_gio_extra_modules="$GIO_EXTRA_MODULES"
    fi
    declare -x GIO_EXTRA_MODULES="$AGENT_GIO_EXTRA_MODULES"
    debug "GIO_EXTRA_MODULES=$GIO_EXTRA_MODULES"

    if [[ ${LD_LIBRARY_PATH-} ]]; then
        local -r old_ld_library_path="$LD_LIBRARY_PATH"
    fi
    declare -x LD_LIBRARY_PATH="$AGENT_LD_LIBRARY_PATH"
    debug "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"

    if [[ ${XDG_DATA_DIRS-} ]]; then
        local -r old_xdg_data_dirs="$XDG_DATA_DIRS"
    fi
    declare -x XDG_DATA_DIRS="$AGENT_XDG_DATA_DIRS"
    debug "XDG_DATA_DIRS=$XDG_DATA_DIRS"

    if [[ ${GST_PLUGIN_SCANNER-} ]]; then
        local -r old_gst_plugin_scanner="$GST_PLUGIN_SCANNER"
    fi
    declare -x GST_PLUGIN_SCANNER="$AGENT_GST_PLUGIN_SCANNER"
    debug "GST_PLUGIN_SCANNER=$GST_PLUGIN_SCANNER"

    if [[ ${GST_PLUGIN_SYSTEM_PATH-} ]]; then
        local -r old_gst_plugin_system_path="$GST_PLUGIN_SYSTEM_PATH"
    fi
    declare -x GST_PLUGIN_SYSTEM_PATH="$AGENT_GST_PLUGIN_SYSTEM_PATH"
    debug "GST_PLUGIN_SYSTEM_PATH=$GST_PLUGIN_SYSTEM_PATH"

    "${cmd[@]}" </dev/null &
    AGENT_PID=$!

    unset CUDA_DEVICE_ORDER
    unset DCV_MODULE_DIR

    if [[ ${old_gio_extra_modules-} ]]; then
        GIO_EXTRA_MODULES="$old_gio_extra_modules"
    else
        unset GIO_EXTRA_MODULES
    fi

    if [[ ${old_ld_library_path-} ]]; then
        LD_LIBRARY_PATH="$old_ld_library_path"
    else
        unset LD_LIBRARY_PATH
    fi

    if [[ ${old_xdg_data_dirs-} ]]; then
        XDG_DATA_DIRS="$old_xdg_data_dirs"
    else
        unset XDG_DATA_DIRS
    fi

    if [[ ${old_gst_plugin_scanner-} ]]; then
        GST_PLUGIN_SCANNER="$old_gst_plugin_scanner"
    else
        unset GST_PLUGIN_SCANNER
    fi

    if [[ ${old_gst_plugin_system_path-} ]]; then
        GST_PLUGIN_SYSTEM_PATH="$old_gst_plugin_system_path"
    else
        unset GST_PLUGIN_SYSTEM_PATH
    fi

    if ! check_process "$AGENT_PID"; then
        error "Cannot launch DCV agent ($AGENT_PID)"
        die "$UNRECOVERABLE_ERROR"
    fi
    debug "DCV agent launched with pid '$AGENT_PID'"
}

launch_Xdcv() {
    local -r xdcv_log="$DCV_LOG_DIR/Xdcv.$USERNAME.$DCV_SESSION_ID.log"

    rotate_log "$DCV_LOG_MAX_FILE_ROTATIONS" "$xdcv_log"

    # create a temporary named pipe needed to obtain the display number
    local -r pipe=$(mktemp -u)
    mkfifo "$pipe"

    # attach it to file descriptor 4
    exec 4<>"$pipe"
    # unlink the named pipe
    rm "$pipe"

    unset DISPLAY
    unset XAUTHORITY
    local xauth_dir
    if [[ ! ${XDG_RUNTIME_DIR-} ]]; then
        if [[ ! ${HOME-} ]]; then
            error "HOME not defined."
            die "$UNRECOVERABLE_ERROR"
        fi
        xauth_dir="${HOME}/.cache/dcv"
        debug "XDG_RUNTIME_DIR is not defined, using $xauth_dir"
    else
        xauth_dir="$XDG_RUNTIME_DIR"
    fi

    # create empty Xauthority file
    local -r xauth_file="$xauth_dir/dcv/$DCV_SESSION_ID.xauth"
    debug "Creating empty Xauthority file '$xauth_file'... "
    mkdir -p -m 700 $(dirname "$xauth_file") && :
    if [[ $? -ne 0 ]]; then
        error "Cannot create directory '$(dirname \"$xauth_file\")' to store the Xauthority file."
        die "$UNRECOVERABLE_ERROR"
    fi

    install -m 600 /dev/null "$xauth_file" && :
    if [[ $? -ne 0 ]]; then
        error "Cannot create authority file '$xauth_file'."
        exec 4<&-
        die "$UNRECOVERABLE_ERROR"
    fi
    debug "Done"

    local hexkey=$(od -An -N16 -tx8 < /dev/urandom | tr -d ' ')

    debug "Initializing Xauthority file: $xauth_file"
    xauth -f "$xauth_file" add ":0" . "$hexkey" && :
    if [[ $? -ne 0 ]]; then
        error "Cannot initialize Xauthority file"
        die "$UNRECOVERABLE_ERROR"
    fi
    debug "Done"

    unset DCV_XDCV_EXT
    unset DCV_GLX_PROVIDER
    if [[ ${DCV_ENABLE_GL-} ]]; then
        message "DCV-GL enabled on '$DCV_SESSION_ID'"

        $(/usr/sbin/dcvgladmin status 32 >/dev/null 2>&1 || /usr/sbin/dcvgladmin status 64 >/dev/null 2>&1) && :
        if [[ $? -eq 0 ]]; then
            debug "DCV-GL enabled, exporting DCV_GLX_PROVIDER"
            declare -x DCV_XDCV_EXT="${DCV_ENABLE_GL}"
            declare -x DCV_GLX_PROVIDER="DCV-GL"
        fi
    fi

    unset DCV_GLX_FALLBACK_PROVIDER
    if [[ ${DCV_ENABLE_GL_FALLBACK-} ]]; then
        message "DCV-GL on '$DCV_SESSION_ID'"

        $(/usr/sbin/dcvgladmin status 32 >/dev/null 2>&1 || /usr/sbin/dcvgladmin status 64 >/dev/null 2>&1) && :
        if [[ $? -eq 0 ]]; then
            debug "DCV-GL fallback enabled, exporting DCV_GLX_FALLBACK_PROVIDER"
            declare -x DCV_XDCV_EXT="${DCV_ENABLE_GL_FALLBACK}"
            declare -x DCV_GLX_FALLBACK_PROVIDER="DCV-GL"
        fi
    fi

    message "Launch Xdcv: $XDCV_BIN -sessionid '$DCV_SESSION_ID' -auth '$xauth_file' -displayfd 4 -logfile '$xdcv_log' $XDCV_ADDITIONAL_ARGS"
    if [ ! -x "$XDCV_BIN" ]; then
        error "Cannot launch Xdcv: '$XDCV_BIN' is not executable"
        die "$UNRECOVERABLE_ERROR"
    fi
    "$XDCV_BIN" -sessionid "$DCV_SESSION_ID" -auth "$xauth_file" -displayfd 4 -logfile "$xdcv_log" $XDCV_ADDITIONAL_ARGS &> /dev/null < /dev/null &
    XDCV_PID=$!
    if ! check_process "$XDCV_PID"; then
        error "Cannot launch Xdcv ($XDCV_PID)"
        exec 4<&-
        die "$UNRECOVERABLE_ERROR"
    fi
    debug "Xdcv process launched with pid '$XDCV_PID'"

    unset DCV_XDCV_EXT
    unset DCV_GLX_PROVIDER

    debug "Reading display number from Xdcv"
    read -t "$DISPLAY_READ_TIMEOUT" XDCV_DISPLAY <&4 && :
    if [[ $? -ne 0 ]]; then
        error "Cannot read display number from Xdcv"
        exec 4<&-
        die "$UNRECOVERABLE_ERROR"
    fi

    debug "Read display number from Xdcv ':$XDCV_DISPLAY'. Closing file descriptor..."

    # close the file descriptor when we are finished
    exec 4<&-

    message "Xdcv started on display ':$XDCV_DISPLAY'"

    if [ "$XDCV_DISPLAY" -ne "0" ]; then
        hexkey=$(od -An -N16 -tx8 < /dev/urandom | tr -d ' ')

        message "Use Xauthority file: $xauth_file"
        xauth -f "$xauth_file" add ":$XDCV_DISPLAY" . "$hexkey" && :
        if [[ $? -ne 0 ]]; then
            error "Cannot update Xauthority file (adding key for display ':$XDCV_DISPLAY')"
            die "$UNRECOVERABLE_ERROR"
        fi

        xauth -f "$xauth_file" remove ":0" && :
        if [[ $? -ne 0 ]]; then
            error "Cannot update Xauthority file (removing key for display ':0')"
            die "$UNRECOVERABLE_ERROR"
        fi
        debug "Done"
    fi

    declare -g -x DISPLAY=":$XDCV_DISPLAY"
    declare -g -x XAUTHORITY="$xauth_file"

    # Use file descript 3 to communicate back to the session launcher the DISPLAY and the XAUTHORITY
    echo "$DISPLAY" >&3
    echo "$XAUTHORITY" >&3
}

launch_init_script() {
    message "Launch init script: $DCV_SESSION_INIT_SCRIPT"
    if [ ! -x "$DCV_SESSION_INIT_SCRIPT" ]; then
        error "Cannot launch init script: '$DCV_SESSION_INIT_SCRIPT' is not executable"
        die "$UNRECOVERABLE_ERROR"
    fi

    local user_id=$(id -u $USERNAME)
    local user_shell=$(getent passwd $user_id | cut -d: -f7) && :
    if [[ $user_shell ]]; then
        declare -x SHELL=$user_shell
    fi
    message "Using shell $SHELL"

    # Launch the init script in a new session
    setsid "$DCV_SESSION_INIT_SCRIPT" "$DCV_SESSION_ID" </dev/null &
    XINIT_PID=$! # this is actually the pid of setsid
    if ! check_process "$XINIT_PID"; then
        error "Cannot launch init script ($XINIT_PID)"
        die "$UNRECOVERABLE_ERROR"
    fi

    XINIT_SID=$(ps -p "$XINIT_PID" --no-headers -o sid)
    message "Init script launched with pid '$XINIT_PID' and sid '$XINIT_SID'"
}

check_children() {
    local must_exit=""

    if ! is_process_alive $XDCV_PID "Xdcv"; then
        must_exit+="[Xdcv]"
    fi

    if ! is_process_alive $XINIT_PID "Init script"; then
        must_exit+="[init]"
    fi

    is_process_alive "$AGENT_PID" "DCV agent" && :

    local -i code=$?

    # FIXME: address the case when the agent needs to restart and when it
    # fails in a unrecoverable way
    case "$code" in
    1)
        must_exit+="[agent]"
        ;;
    2)
        if [[ ! $must_exit ]]; then
            wait "$AGENT_PID" && :
            local -i exit_value=$?
            if [[ "$exit_value" -ne "$DCV_AGENT_STATUS_UNRECOVERABLE_ERROR" ]]; then
                # FIXME: we need a logic to limit the number of relaunches
                message "Relaunching DCV agent (exit value: $exit_value)..."
                launch_agent
            else
                must_exit+="[agent]"
                message "DCV agent [$AGENT_PID] terminated with unrecoverable error (exit value: $exit_value)"
            fi
        fi
        ;;
    *)
        ;;
    esac

    if [[ $must_exit ]]; then
        message "Exit reason: $must_exit"

        # FIXME we should exit with the exit code of the init script
        die 0
    fi
}

handle_chld_trap() {
    debug "Handling CHLD trap"

    if [[ $EXITING -ne 0 ]]; then
       return
    fi

    check_children
}

watchdog_children() {
    while :
    do
        debug "Starting watchdog cycle..."
        wait -n && :

        if [[ $EXITING -ne 0 ]]; then
            break
        fi

        check_children
    done
}

main() {
    check_mandatory_env_vars

    local -r session_log="$DCV_LOG_DIR/dcv-xsession.$USERNAME.$DCV_SESSION_ID.log"

    rotate_log "$DCV_LOG_MAX_FILE_ROTATIONS" "$session_log"

    exec >"$session_log" 2>&1

    # Remove temporary log file
    cat "$TMP_SESSION_LOG"
    rm "$TMP_SESSION_LOG"

    # Use file descriptor 3 to communicate back to the session launcher the PID of the process
    echo "$$" >&3

    # The watchdog strategy requires 'wait -n' that has been introduced with bash 4.3
    # https://tiswww.case.edu/php/chet/bash/CHANGES
    if [[ -z $BASH_MAJOR || -z $BASH_MINOR ]]; then
        warn "Could not determine bash shell version"
        USE_CHILDREN_WATCHDOG=0
    elif [[ $BASH_MAJOR -lt 4 ]]; then
        debug "Bash version $BASH_MAJOR.$BASH_MINOR < 4.0"
        USE_CHILDREN_WATCHDOG=0
    elif [[ $BASH_MAJOR -eq 4 && $BASH_MINOR -lt 3 ]]; then
        debug "Bash version $BASH_MAJOR.$BASH_MINOR < 4.3"
        USE_CHILDREN_WATCHDOG=0
    fi

    # Decide job control strategy
    if [[ $USE_CHILDREN_WATCHDOG -eq 0 ]]; then
        debug "Job control strategy set to CHLD trap"
        trap '
            handle_chld_trap
        ' CHLD
    else
        debug "Job control strategy set to WatchDog"
    fi

    trap '
        message "Handling SIGINT and SIGTERM"
        die 0
    ' INT TERM # For safe cleanup

    checks_optional_env_vars

    if [ $LOG_VERBOSITY -ge 4 ]; then
        debug "Execution environment:"
        echo
        env
        echo "========="
    fi

    launch_Xdcv

    launch_agent "ROTATE"

    launch_init_script

    if [[ $USE_CHILDREN_WATCHDOG -eq 1 ]]; then
        watchdog_children
    else
        set -m
        wait && :
        set +m

        message "No more children"
    fi
}

main "$@"

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