Skip to content
Snippets Groups Projects
Commit c0842d08 authored by Simon McVittie's avatar Simon McVittie
Browse files

bin: Add a simpler interface to wrap a game in the launcher-service


If launcher-service is (mostly) mechanism, then this is policy.
It uses argv[1] and $STEAM_COMPAT_LAUNCHER_SERVICE to decide whether
to wrap a launcher-service around a game process. The idea is that
Steam will set STEAM_COMPAT_LAUNCHER_SERVICE to the most appropriate
layer of the stack at which to be inserting arbitrary debugging
commands into a game.

In previous prototypes, this was open-coded into each compat tool, but
centralizing it is a lot easier for compat tool authors. Put a version
in the name we use, to ensure that if we find we need to make breaking
changes, we can rename to a new interface version (and optionally keep
the old one around too).

To minimize its startup time cost, this doesn't use GLib, only glibc.

Signed-off-by: default avatarSimon McVittie <smcv@collabora.com>
parent 45d542a6
No related branches found
No related tags found
1 merge request!465Make launcher-service integration easier
/*
* steam-runtime-launcher-interface-0 — convenience interface for compat tools
*
* Copyright © 2022 Collabora Ltd.
* SPDX-License-Identifier: MIT
*/
/*
* Note that because this is on the critical path for running a game,
* and because it doesn't actually do very much, it intentionally
* does not depend on GLib or do non-trivial command-line parsing, etc.
*/
#include <argz.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define NAME "steam-runtime-launcher-interface-0"
/* Chosen to be similar to env(1) */
enum
{
LAUNCH_EX_USAGE = 125,
LAUNCH_EX_FAILED = 125,
LAUNCH_EX_CANNOT_INVOKE = 126,
LAUNCH_EX_NOT_FOUND = 127,
LAUNCH_EX_CANNOT_REPORT = 128
};
#if 0
#define trace(...) do { \
fprintf (stderr, "%s: trace: ", NAME); \
fprintf (stderr, __VA_ARGS__); \
fprintf (stderr, "\n"); \
} while (0)
#else
#define trace(...) do { } while (0)
#endif
static int
usage (void)
{
fprintf (stderr,
"Usage: %s TOOL-NAME[:TOOL-NAME...] COMMAND [ARGUMENTS]\n",
NAME);
return LAUNCH_EX_USAGE;
}
static void
log_oom (void)
{
fprintf (stderr, "%s: %s\n", NAME, strerror (ENOMEM));
}
static inline void
indirect_free (void *location)
{
void **pointer_to_pointer = location;
free (*pointer_to_pointer);
}
static void *
steal_pointer (void *location)
{
void **pointer_to_pointer = location;
void *value = *pointer_to_pointer;
*pointer_to_pointer = NULL;
return value;
}
#define autofree __attribute__((__cleanup__ (indirect_free)))
static bool
want_launcher_service (const char *tool_names)
{
const char *requested = getenv ("STEAM_COMPAT_LAUNCHER_SERVICE");
autofree char *needle = NULL;
autofree char *haystack = NULL;
trace ("Checking whether %s contains %s", tool_names, requested);
if (requested == NULL)
return false;
if (strchr (requested, ':') != NULL)
{
fprintf (stderr,
"%s: Expected a single entry in $STEAM_COMPAT_LAUNCHER_SERVICE\n",
NAME);
return false;
}
if (asprintf (&needle, ":%s:", requested) < 0)
{
log_oom ();
return false;
}
if (asprintf (&haystack, ":%s:", tool_names) < 0)
{
log_oom ();
return false;
}
if (strstr (haystack, needle) != NULL)
return true;
return false;
}
static void *
log_if_oom (void *copy)
{
if (copy == NULL)
log_oom ();
return copy;
}
#define N_ELEMENTS(arr) (sizeof (arr) / sizeof (arr[0]))
static char *
find_launcher_service (void)
{
static const char * const search_dirs[] =
{
"/run/pressure-vessel/pv-from-host/bin",
"/usr/lib/pressure-vessel/from-host/bin",
};
size_t i;
const char *env;
autofree char *argz = NULL;
size_t argz_len = 0;
/* Check for environment variable override */
env = getenv ("SRT_LAUNCHER_SERVICE");
if (env != NULL
&& access (env, X_OK) == 0)
return log_if_oom (strdup (env));
/* Check for the version provided by pressure-vessel, which if anything
* is probably newer than the one in the container's PATH */
for (i = 0; i < N_ELEMENTS (search_dirs); i++)
{
autofree char *path = NULL;
if (asprintf (&path, "%s/steam-runtime-launcher-service", search_dirs[i]) < 0)
{
log_oom ();
return NULL;
}
if (access (path, X_OK) == 0)
return steal_pointer (&path);
}
/* Check the PATH */
env = getenv ("PATH");
if (env != NULL)
{
char *next = NULL;
if (argz_add_sep (&argz, &argz_len, env, ':') != 0)
{
log_oom ();
return NULL;
}
for (next = argz_next (argz, argz_len, NULL);
next != NULL;
next = argz_next (argz, argz_len, next))
{
autofree char *path = NULL;
if (asprintf (&path,
"%s/steam-runtime-launcher-service",
next) < 0)
{
log_oom ();
return NULL;
}
if (access (path, X_OK) == 0)
return steal_pointer (&path);
}
}
argz_len = 0;
free (steal_pointer (&argz));
/* As a last resort, check in all Steam compat tools */
env = getenv ("STEAM_COMPAT_TOOL_PATHS");
if (env != NULL)
{
char *next = NULL;
if (argz_add_sep (&argz, &argz_len, env, ':') != 0)
{
log_oom ();
return NULL;
}
for (next = argz_next (argz, argz_len, NULL);
next != NULL;
next = argz_next (argz, argz_len, next))
{
autofree char *path = NULL;
if (asprintf (&path,
"%s/pressure-vessel/bin/steam-runtime-launcher-service",
next) < 0)
{
log_oom ();
return NULL;
}
if (access (path, X_OK) == 0)
return steal_pointer (&path);
}
}
argz_len = 0;
free (steal_pointer (&argz));
fprintf (stderr, "%s: Cannot find steam-runtime-launcher-service\n", NAME);
return NULL;
}
int
main (int argc, char **argv)
{
const char *tool_names;
autofree char *launcher_service = NULL;
int i;
if (argc < 3)
return usage ();
if (argv[1][0] == '-')
{
fprintf (stderr, "%s does not accept any --options", NAME);
return usage ();
}
if (argv[2][0] == '-')
{
fprintf (stderr, "%s does not accept any --options", NAME);
return usage ();
}
tool_names = argv[1];
trace ("Starting tool %s, wrapped program %s", tool_names, argv[2]);
if (want_launcher_service (tool_names)
&& (launcher_service = find_launcher_service ()) != NULL)
{
autofree char *argz = NULL;
size_t argz_len = 0;
autofree char **launcher_service_argv = NULL;
trace ("Trying to run launcher service: %s", launcher_service);
if (argz_add (&argz, &argz_len, launcher_service) != 0
|| argz_add (&argz, &argz_len, "--exec-fallback")
|| argz_add (&argz, &argz_len, "--hint")
|| argz_add (&argz, &argz_len, "--no-stop-on-name-loss")
|| argz_add (&argz, &argz_len, "--replace")
|| argz_add (&argz, &argz_len, "--session")
|| argz_add (&argz, &argz_len, "--"))
{
log_oom ();
goto fallback;
}
for (i = 2; i < argc; i++)
{
if (argz_add (&argz, &argz_len, argv[i]) != 0)
{
log_oom ();
goto fallback;
}
}
launcher_service_argv = calloc (argz_count (argz, argz_len) + 1,
sizeof (char *));
if (launcher_service_argv == NULL)
{
log_oom ();
goto fallback;
}
unsetenv ("STEAM_COMPAT_LAUNCHER_SERVICE");
argz_extract (argz, argz_len, launcher_service_argv);
execvp (launcher_service, launcher_service_argv);
fprintf (stderr, "%s: execvp %s: %s\n",
NAME, launcher_service, strerror (errno));
fallback:
fprintf (stderr,
"%s: Cannot run launcher service, falling back to "
"running command without it\n",
NAME);
}
execvp (argv[2], &argv[2]);
if (errno == ENOENT)
return LAUNCH_EX_NOT_FOUND;
return LAUNCH_EX_CANNOT_INVOKE;
}
---
title: steam-runtime-launcher-interface-0
section: 1
...
<!-- This document:
Copyright © 2020-2022 Collabora Ltd.
SPDX-License-Identifier: MIT
-->
# NAME
steam-runtime-launcher-interface-0 - entry point for steam-runtime-launcher-service
# SYNOPSIS
**steam-runtime-launcher-interface-0**
*TOOL NAME*[**:**_TOOL NAME_...]
*COMMAND* [*ARGUMENTS*]
# DESCRIPTION
**steam-runtime-launcher-interface-0** runs an arbitrary command and
automatically detects whether to wrap the
**steam-runtime-launcher-service**(1) helper service around it.
It is intended to be run by Steam compatibility tools such as Proton
and the Steam container runtime.
Steam can request that a particular compatibility tool gets an associated
**steam-runtime-launcher-service**(1) by setting the
**STEAM_COMPAT_LAUNCHER_SERVICE** environment variable.
At least two positional parameters are required.
The first parameter is a tool name to be matched against the environment
variable set by Steam, or a **:**-separated list of alternative names
for the same tool.
It must not start with **-**, which is reserved for possible future use.
If more than one name is listed, they are treated as equivalent names for
the same compatibility tool, and the command server will be activated
if Steam requests any of the given names: for example,
`scout-in-container:scout-on-soldier:1070560` could all be used as names
for the "Steam Linux Runtime" compatibility tool.
If the environment variable does not match any of the specified tool
names, **steam-runtime-launcher-interface-0** runs the *COMMAND*
with no further modification.
If the environment variable matches any of the specified tool names,
then **steam-runtime-launcher-interface-0** runs
**steam-runtime-launcher-service**(1) as a wrapper around the *COMMAND*.
Debugging commands can be run in the same execution environment
as the *COMMAND* by passing them to **steam-runtime-launch-client**(1).
This is a versioned interface.
If incompatible behaviour changes are made in a future
version of the Steam Runtime, the tool should be renamed to
**steam-runtime-launcher-interface-1**.
# ENVIRONMENT
`STEAM_COMPAT_LAUNCHER_SERVICE` (string)
: A tool name which is matched against the first parameter.
`SRT_LAUNCHER_SERVICE_STOP_ON_EXIT` (boolean)
: By default, or if set to `1`, the **steam-runtime-launcher-service**(1)
will terminate other launched processes and prepare to exit when
the *COMMAND* does.
This is appropriate when interacting with a game or other program
that is mostly working: it cleans up any associated debugging
processes when the game itself exits, so that Steam will correctly
conside the game to have exited.
If set to `0`, the **steam-runtime-launcher-service**(1) will not
stop until it is explicitly terminated by **SIGTERM** or
**steam-runtime-launch-client --bus-name=... --terminate**.
This is particularly useful when debugging a game that crashes or
otherwise exits on startup.
As long as the **steam-runtime-launcher-service**(1) continues
to run, Steam will behave as though the game was still running:
the user is responsible for terminating the service when it is no
longer needed.
This is a generic **steam-runtime-launcher-service**(1) option,
and is not specific to **steam-runtime-launcher-interface-0**.
`SRT_LAUNCHER_SERVICE_STOP_ON_NAME_LOSS` (ignored)
: Ignored.
For robustness, the **steam-runtime-launcher-service**(1) run from
this tool always behaves as though this variable is set to `0`.
Even if its D-Bus well-known name is taken over by another process,
it can still be contacted via its D-Bus unique name.
# OUTPUT
Unstructured diagnostic messages are printed on standard error.
The *COMMAND* inherits standard output and standard error.
# EXIT STATUS
0
: The *COMMAND* exited successfully
125
: Internal error in **steam-runtime-launcher-interface-0**,
or incorrect command-line options
126
: The *COMMAND* could not be started
Any nonzero status
: The *COMMAND* exited unsuccessfully
# EXAMPLE
When this tool has been integrated into Steam and Proton, if a Proton
game is crashing on startup, it will be possible to set its Steam
launch options to
SRT_LAUNCHER_SERVICE_STOP_ON_EXIT=0 STEAM_COMPAT_LAUNCHER_SERVICE=proton %command%
and then run debugging commands in its Proton environment with a command
like:
~/.steam/root/ubuntu12_32/steam-runtime/amd64/bin/steam-runtime-launch-client \
--bus-name=com.steampowered.App312990 \
--directory="" \
-- \
.../Proton\ -\ Experimental/files/bin/wine64 winedbg Expendabros.exe
To exit the command server when finished, use a command like:
~/.steam/root/ubuntu12_32/steam-runtime/amd64/bin/steam-runtime-launch-client \
--bus-name=com.steampowered.App312990 \
--terminate
<!-- vim:set sw=4 sts=4 et: -->
......@@ -84,6 +84,13 @@ executable(
install_rpath : bin_rpath,
)
executable(
'steam-runtime-launcher-interface-0',
'launcher-interface-0.c',
c_args : srt_c_args,
install : true,
)
executable(
'steam-runtime-launcher-service',
sources: [
......@@ -159,6 +166,7 @@ if build_man_pages
'input-monitor',
'launch-client',
'launch-options',
'launcher-interface-0',
'launcher-service',
'steam-remote',
'system-info',
......
......@@ -2,6 +2,7 @@ usr/bin/steam-runtime-check-requirements
usr/bin/steam-runtime-input-monitor
usr/bin/steam-runtime-launch-client
usr/bin/steam-runtime-launch-options
usr/bin/steam-runtime-launcher-interface-*
usr/bin/steam-runtime-launcher-service
usr/bin/steam-runtime-system-info
usr/share/man/man1/steam-runtime-*
......@@ -106,6 +106,7 @@ EXECUTABLES = [
'pressure-vessel-wrap',
'pv-bwrap',
'steam-runtime-launch-client',
'steam-runtime-launcher-interface-0',
'steam-runtime-launcher-service',
'steam-runtime-system-info',
]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment