Skip to content
Snippets Groups Projects
_v2-entry-point 8.13 KiB
Newer Older
#!/usr/bin/env bash

set -eu
me="$(readlink -f "$0")"
here="${me%/*}"
me="${me##*/}"

# This is a prototype and will probably not survive in its current form.
# Don't rely on it.

deploy=
is_main=yes
suite=scout
verbose=

if [ "${PRESSURE_VESSEL_VERBOSE-}" = 1 ]; then
    verbose=yes
fi

log () {
    printf '%s\n' "${me}[$$]: $*" >&2
}

verbose () {
    if [ -n "$verbose" ]; then
        log "$@"
    fi
}

verbose "argv: $(printf '%q ' "$@")"

usage () {
    local code="$1"
    shift

    if [ "$code" -ne 0 ]; then
        exec >&2
    fi

    echo "Usage:"
    echo "$me [OPTIONS] COMMAND [ARGS...]"
    echo
    echo "Run a command in a container that is shared with other"
    echo "invocations of the same Steam game."
    echo
    echo "Required arguments"
    echo "COMMAND [ARGS...] Run this."
    echo
    echo "Options"
    echo "--deploy=DIR      Deploy to this directory [default=SUITE]."
    echo "--suite=SUITE     Run in this runtime [default=scout]."
    echo "--verb=%verb%     Mode to operate in [default=waitforexitandrun]."
    echo "--verbose         Be more verbose."

    exit "${code}"
}

getopt_temp="help"
getopt_temp="${getopt_temp},deploy:"
getopt_temp="${getopt_temp},suite:"
getopt_temp="${getopt_temp},verb:"
getopt_temp="${getopt_temp},verbose"

getopt_temp="$(getopt -o '' --long "$getopt_temp" -n "$me" -- "$@")"
eval "set -- $getopt_temp"
unset getopt_temp

while [ "$#" -gt 0 ]; do
    case "$1" in
        (--help)
            usage 0
            # not reached
            ;;

        (--deploy)
            deploy="$2"
            shift 2
            ;;

        (--suite)
            suite="$2"
            shift 2
            ;;

        (--verb)
            case "$2" in
                (waitforexitandrun)
                    is_main=yes
                    ;;
                (*)
                    is_main=
                    ;;
            esac
            shift 2
            ;;

        (--verbose)
            verbose=yes
            shift
            ;;

        (--)
            shift
            break
            ;;

        (-*)
            log "Unknown option: $1"
            usage 125   # EX_USAGE from sysexits.h
            # not reached
            ;;

        (*)
            break
            ;;
    esac
done

if [ -z "$deploy" ]; then
    deploy="$suite"
fi

if [ "$#" -eq 0 ] || [ "x$1" = x-- ]; then
    log "Error: A command to run is required"
    usage 125
fi

# Steam App ID of this app or game
log "STEAM_COMPAT_APP_ID=${STEAM_COMPAT_APP_ID-}"

# Session ID used for coordination between processes involving the same game.
# If unset/empty, only execute a single command.
log "STEAM_COMPAT_SESSION_ID=${STEAM_COMPAT_SESSION_ID-}"

# Used to create sockets.
# A future version will fall back to /tmp, but only when this can be
# done securely.
log "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR-}"
# In non-session mode, we always behave like the main program
if [ -z "${STEAM_COMPAT_SESSION_ID-}" ]; then
    is_main=yes
fi

# At this point $LD_LIBRARY_PATH might
# 1. be equal to $STEAM_RUNTIME_LIBRARY_PATH (e.g. a game without
#    any special launch options).
# 2. contain all the entries of $STEAM_RUNTIME_LIBRARY_PATH, plus eventually
#    any additional paths from the game launch options (e.g. a game launched
#    with "LD_LIBRARY_PATH=/my_game_path:${LD_LIBRARY_PATH} %command%")
# 3. contain just new entries from the game launch options (e.g. a game
#    launched with "LD_LIBRARY_PATH=/my_game_path %command%")
# 4. being unset or empty (e.g. a game launched with
#    "LD_LIBRARY_PATH="" %command%")
#
# We extract all the entries from $LD_LIBRARY_PATH that are not under the
# $STEAM_RUNTIME paths. In this way we should end up with a list of paths
# that are from the system $LD_LIBRARY_PATH, the system "ldconfig" and the
# manually set $LD_LIBRARY_PATH paths from the game launch options.
case "${STEAM_RUNTIME-}" in
    (/*)
        oldIFS="$IFS"
        IFS=:
        paths=

        for path in ${LD_LIBRARY_PATH-}; do
            if [ "${path}" == "${STEAM_RUNTIME}" ]; then
                # path is exactly the ${STEAM_RUNTIME}; ignore
                continue
            elif [ "${path#${STEAM_RUNTIME}/}" != "${path}" ]; then
                # path is ${STEAM_RUNTIME}/...; ignore
                continue
            else
                # keep it (note that we discard the extra leading ":" later)
                paths="${paths}:${path}"
            fi
        done

        IFS="$oldIFS"
        export PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="${paths#:}"
        ;;
    (*)
        # use LD_LIBRARY_PATH as-is
        export PRESSURE_VESSEL_APP_LD_LIBRARY_PATH="${LD_LIBRARY_PATH-}"
        ;;
esac

unset LD_LIBRARY_PATH
unset STEAM_RUNTIME

pressure_vessel="${PRESSURE_VESSEL_PREFIX:-"${here}/pressure-vessel"}"

# Arguments for pressure-vessel-wrap, currently none
declare -a container_args=()

if [ -z "${STEAM_COMPAT_SESSION_ID-}" ]; then
    # Run as a single command
    exec "$here/run-in-steamrt" \
        --arch=amd64,i386 \
        --deploy \
        --runtime=com.valvesoftware.SteamRuntime.Platform \
        --suite="${suite}" \
        "${deploy}" \
        -- \
        ${container_args[0]+"${container_args[@]}"} \
# Session mode. Either PRESSURE_VESSEL_SOCKET_DIR or XDG_RUNTIME_DIR are required.
if [[ -v PRESSURE_VESSEL_SOCKET_DIR ]]; then
    if ! [ -O "$PRESSURE_VESSEL_SOCKET_DIR" ]; then
        log "PRESSURE_VESSEL_SOCKET_DIR is owned by someone else"
        exit 125
    fi
    rendezvous="${PRESSURE_VESSEL_SOCKET_DIR}/SteamLinuxRuntime.${STEAM_COMPAT_SESSION_ID:?}"
else
    if ! [ -O "$XDG_RUNTIME_DIR" ]; then
        log "XDG_RUNTIME_DIR is owned by someone else"
        exit 125
    fi
    rendezvous="${XDG_RUNTIME_DIR}/SteamLinuxRuntime.${STEAM_COMPAT_SESSION_ID:?}"
if mkdir -m700 "$rendezvous" 2>/dev/null; then
    :
elif [ -d "$rendezvous" ] && [ -O "$rendezvous" ]; then
    :
else
    log "Unable to create or adopt $rendezvous"
    exit 125
fi

terminate_container () {
    "$pressure_vessel/bin/pressure-vessel-adverb" \
        --create \
        --wait \
        --write \
        --lock-file "${rendezvous}/.ref" \
        -- \
    "$pressure_vessel/bin/pressure-vessel-launch" \
        --socket="$rendezvous/socket" \
        --terminate
}

if [ -n "$is_main" ] && [ -n "$rendezvous" ]; then
    trap terminate_container EXIT
fi

# We have to launch pressure-vessel-launcher(1) *without* setting
# SteamAppId, SteamAppUser, etc., otherwise Steam will think the game
# is already running and refuse to run it again. (Tested with SteamAppId,
# just guessing about the others)
"$pressure_vessel/bin/pressure-vessel-adverb" \
    --create \
    --wait \
    --write \
    --lock-file "${rendezvous}/.ref" \
    -- \
env \
    -u LD_PRELOAD \
    -u FONTCONFIG_PATH \
    -u SteamLauncherUI \
    -u SteamAppId \
    -u SteamAppUser \
    -u SteamClientLaunch \
    -u SteamEnv \
    -u SteamGameId \
    -u SteamOverlayGameId \
"${here}/_start-container-in-background" \
    --session="$rendezvous" \
    --suite="$suite" \
    -- \
    ${container_args[0]+"${container_args[@]}"} \
    if [ "${SteamAppId-}" != "${STEAM_COMPAT_APP_ID-}" ]; then
        log "Mismatch: SteamAppId=${SteamAppId-}, STEAM_COMPAT_APP_ID=${STEAM_COMPAT_APP_ID-}"
        exit 125
    fi

    launch_args=( \
        ${launch_args[0]+"${launch_args[@]}"} \
        --pass-env-matching '*' \
        --terminate \
    )

    # For the main game we wait for all processes to terminate before
    # shutting down the container.
    set -- \
        "$pressure_vessel/bin/pressure-vessel-adverb" \
        ${PRESSURE_VESSEL_SHELL+--shell="$PRESSURE_VESSEL_SHELL"} \
        ${PRESSURE_VESSEL_TERMINAL+--terminal="$PRESSURE_VESSEL_TERMINAL"} \
        ${launch_args[0]+"${launch_args[@]}"} \
        --unset-env LD_PRELOAD \
    )
fi

exec "$pressure_vessel/bin/pressure-vessel-launch" \
    --directory="$(pwd)" \
    --socket="$rendezvous/socket" \
    ${launch_args[0]+"${launch_args[@]}"} \
    -- \
    "$@"
exit 125
# not reached

# vim:set sw=4 sts=4 et: