Permalink
a1c2235 Dec 24, 2017
410 lines (352 sloc) 13 KB
/*
* This file is part of linux-steam-integration.
*
* Copyright © 2016-2017 Ikey Doherty <ikey@solus-project.com>
*
* linux-steam-integration is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <glob.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../common/files.h"
#include "../common/log.h"
#include "../nica/files.h"
#include "config.h"
#include "lsi.h"
#include "shim.h"
/**
* Required to force Steam into 32-bit detection mode, which is useful for
* recent issues like the CS:GO 64-bit update with huge FPS drops
*/
#define EMUL32BIN "linux32"
/**
* Audit path is used for the libintercept library to ensure Steam only uses the
* host SDL, etc.
*/
#define AUDIT_PATH "/usr/\$LIB/liblsi-intercept.so"
/**
* Redirect path is used for the libredirect library to perform internal
* redirections in functions like `open()` where needed to hotfix games
* at runtime.
*/
#define REDIRECT_PATH "/usr/\$LIB/liblsi-redirect.so"
/**
* Potential for the host Vulkan ICD files (old vs new)
*/
#define VK_GLOB "/var/lib/snapd/lib/gl/*nvidia*.json"
#define VK_GLOB_2 "/var/lib/snapd/lib/vulkan/*nvidia*.json"
/**
* Used to update a value in the environment, and perform a prepend if the variable
* is already set.
*/
static void shim_export_merge_vars(const char *var_name, const char *prefix, const char *value)
{
static char copy_buffer[PATH_MAX] = { 0 };
const char *env_exist = NULL;
int ret = 0;
env_exist = getenv(var_name);
ret = snprintf(copy_buffer,
sizeof(copy_buffer),
"%s%s%s%s",
prefix ? prefix : "",
value,
env_exist ? ":" : "",
env_exist ? env_exist : "");
if (ret < 0) {
lsi_log_error("failed to update variable '%s'", var_name);
return;
}
lsi_log_debug("%s = %s", var_name, copy_buffer);
setenv(var_name, copy_buffer, 1);
}
/**
* Set up the LD_AUDIT environment - respecting $SNAP if set
*/
static void shim_set_audit_path(const char *prefix)
{
shim_export_merge_vars("LD_AUDIT", prefix, AUDIT_PATH);
}
/**
* Set up LD_PRELOAD, respecting an existing LD_PRELOAD and forcing ourselves
* to be first in the list.
*/
static void shim_set_ld_preload(const char *prefix)
{
shim_export_merge_vars("LD_PRELOAD", prefix, REDIRECT_PATH);
}
#ifdef HAVE_SNAPD_SUPPORT
/**
* This function is only used during our initial bootstrap phase to ensure
* we're able to set up the environment and directories correctly under the
* snapd system.
*/
static void shim_init_user(const char *userdir)
{
static const char *paths[] = {
".local/share",
".config",
".cache",
};
static const char *vars[] = {
"XDG_DATA_HOME",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
};
static char tgt[PATH_MAX] = { 0 };
for (size_t i = 0; i < ARRAY_SIZE(paths); i++) {
if (snprintf(tgt, sizeof(tgt), "%s/%s", userdir, paths[i]) < 0) {
lsi_log_error("memory failure");
return;
}
if (!lsi_file_exists(tgt)) {
if (!nc_mkdir_p(tgt, 00755)) {
lsi_log_error("failed to construct %s: %s", tgt, strerror(errno));
goto write_var;
}
lsi_log_debug("Constructing %s: %s", vars[i], tgt);
}
write_var:
setenv(vars[i], tgt, 1);
}
}
/**
* Attempt to set up Vulkan in the environment by looking for the ICD files
* passed from the hostfs
*/
static bool shim_init_vulkan(const char *glob_path)
{
glob_t glo = { 0 };
glob(glob_path, GLOB_DOOFFS, NULL, &glo);
if (glo.gl_pathc < 1) {
globfree(&glo);
return false;
}
/* Preserve glob order and insert environment in reverse */
for (int i = glo.gl_pathc - 1; i >= 0; i--) {
shim_export_merge_vars("VK_ICD_FILENAMES", NULL, glo.gl_pathv[i]);
}
globfree(&glo);
return true;
}
#endif
#ifdef HAVE_SNAPD_SUPPORT
/**
* Attempt to push a path into the variable name if it actually exists
*/
static void shim_export_ld_dir(const char *var_name, const char *dir)
{
if (!lsi_file_exists(dir)) {
return;
}
shim_export_merge_vars(var_name, NULL, dir);
}
/**
* Set up any extra environment pieces that might need fixing
*
* Currently this only sets up the snapd environmental variables, so that
* we don't rely on separate bootstrap scripts out of tree.
*/
static void shim_export_extra(const char *prefix)
{
static const char *snap_user = NULL;
static const char *xdg_home = NULL;
/* Add all of these guys to LD_LIBRARY_PATH if they exist.
* This allows us to handle the special-case multiarch mounts.
*/
static const char *ld_library_dirs[] = {
"/var/lib/snapd/lib/gl/vdpau", /**<64-bit vdpau */
"/var/lib/snapd/lib/gl32/vdpau", /**<32bit vdpau */
"/usr/lib/glx-provider/default", /**<Solus mesa, 64-bit */
"/usr/lib32/glx-provider/default", /**<Solus mesa, 32bit */
"/var/lib/snapd/lib/gl", /**<Potential host NVIDIA libraries */
"/var/lib/snapd/lib/gl32", /**<Potential host NVIDIA 32-bit libraries (new location) */
};
/* Add any necessary DRI drivers to the path, though in reality this
* shouldn't REALLY be needed, but let's just play it safe.
*/
static const char *dri_drivers_extra[] = {
"/usr/lib32/dri",
"/usr/lib/dri",
};
unsetenv("LIBGL_DRIVERS_PATH");
unsetenv("LD_LIBRARY_PATH");
/* Include all GLI driver paths */
for (size_t i = 0; i < ARRAY_SIZE(dri_drivers_extra); i++) {
shim_export_ld_dir("LIBGL_DRIVERS_PATH", dri_drivers_extra[i]);
shim_export_ld_dir("LD_LIBRARY_PATH", dri_drivers_extra[i]);
}
/* Now set the library path accordingly */
for (size_t i = 0; i < ARRAY_SIZE(ld_library_dirs); i++) {
shim_export_ld_dir("LD_LIBRARY_PATH", ld_library_dirs[i]);
}
/* the vdpau directory only exists on multiarch */
if (lsi_file_exists("/var/lib/snapd/lib/gl/vdpau")) {
shim_export_ld_dir("VDPAU_DRIVER_PATH", "/var/lib/snapd/lib/gl/vdpau");
} else {
shim_export_ld_dir("VDPAU_DRIVER_PATH", "/var/lib/snapd/lib/gl");
}
/* Path */
shim_export_merge_vars("PATH", prefix, "/usr/bin");
shim_export_merge_vars("PATH", prefix, "/bin");
/* We require our PulseAudio fake script to be seen first. */
shim_export_merge_vars("PATH", prefix, FAKE_SCRIPTS_DIR "/pulseaudio");
/* Try both known Vulkan ICD paths */
if (!shim_init_vulkan(VK_GLOB_2)) {
shim_init_vulkan(VK_GLOB);
}
/* XDG */
shim_export_merge_vars("XDG_CONFIG_DIRS", NULL, "/etc/xdg");
shim_export_merge_vars("XDG_CONFIG_DIRS", NULL, "/usr/share/xdg");
shim_export_merge_vars("XDG_CONFIG_DIRS", prefix, "/etc/xdg");
shim_export_merge_vars("XDG_CONFIG_DIRS", prefix, "/usr/xdg");
shim_export_merge_vars("XDG_DATA_DIRS", prefix, "/usr/share");
snap_user = getenv("SNAP_USER_COMMON");
shim_export_merge_vars("XDG_DATA_DIRS", NULL, "/usr/share");
shim_export_merge_vars("XDG_DATA_DIRS", prefix, "/usr/share");
if (snap_user) {
shim_export_merge_vars("XDG_DATA_DIRS", NULL, snap_user);
shim_init_user(snap_user);
}
/* Ensure XDG_RUNTIME_DIR really exists */
xdg_home = getenv("XDG_RUNTIME_DIR");
if (!xdg_home) {
return;
}
if (lsi_file_exists(xdg_home)) {
return;
}
if (!nc_mkdir_p(xdg_home, 00755)) {
lsi_log_error("Failed to setup XDG_RUNTIME_DIR %s: %s", xdg_home, strerror(errno));
return;
}
lsi_log_debug("Constructed XDG_RUNTIME_DIR: %s", xdg_home);
}
#else
static void shim_export_extra(__lsi_unused__ const char *prefix)
{
}
#endif
static LsiConfig lsi_config = { 0 };
/* Public API */
bool shim_bootstrap()
{
const char *operation_prefix = NULL;
#ifdef HAVE_SNAPD_SUPPORT
/* Snapd specific prefix */
operation_prefix = getenv("SNAP");
#endif
/* We might have additional variables we need to export and we
* might also end up changing XDG_CONFIG_HOME - ensure we actually
* read our settings from the right place. */
shim_export_extra(operation_prefix);
/* Ensure we now have some kind of config */
if (!lsi_config_load(&lsi_config)) {
lsi_config_load_defaults(&lsi_config);
}
/* Force STEAM_RUNTIME into the environment */
if (lsi_config.use_native_runtime) {
/* Explicitly disable the runtime */
setenv("STEAM_RUNTIME", "0", 1);
#ifdef HAVE_LIBINTERCEPT
/* Only use libintercept in combination with native runtime! */
if (lsi_config.use_libintercept) {
shim_set_audit_path(operation_prefix);
}
#endif
#ifdef HAVE_LIBREDIRECT
/* Only use libredirect in combination with native runtime! */
if (lsi_config.use_libredirect) {
shim_set_ld_preload(operation_prefix);
}
/* And unity hack is dependent on libredirect.. */
if (lsi_config.use_unity_hack) {
setenv("LSI_USE_UNITY_HACK", "1", 1);
}
#endif
} else {
/* Only preload when needed. */
if (lsi_system_requires_preload()) {
shim_export_merge_vars("LD_PRELOAD", operation_prefix, lsi_preload_list());
}
setenv("STEAM_RUNTIME", "1", 1);
}
/* Vanilla dbus users suffer a segfault on Steam exit, due to incorrect
* usage of dbus by Steam. Help them out */
setenv("DBUS_FATAL_WARNINGS", "0", 1);
/* Requires Solus patch to actually work */
setenv("DBUS_SILENCE_WARNINGS", "1", 1);
/* A recent regression is to interpret XMODIFIERS and then fail to
* make it beyond `SDL_InitSubSystem` due to `InitIME` failing to
* properly use D-BUS ...
*/
unsetenv("XMODIFIERS");
unsetenv("GTK_MODULES");
/* All done now. */
return true;
}
static int shim_execute_internal(const char *command, int argc, char **argv, bool use_path)
{
bool is_x86_64;
const char *n_argv[argc + 3];
const char *exec_command = NULL;
int i = 1;
int8_t off = 1;
int (*vfunc)(const char *, char *const argv[]) = NULL;
is_x86_64 = lsi_system_is_64bit();
memset(&n_argv, 0, sizeof(char *) * (argc + 3));
/* If we're 64-bit and 32-bit is forced, proxy via linux32 */
if (lsi_config.force_32 && is_x86_64) {
exec_command = EMUL32BIN;
n_argv[0] = EMUL32BIN;
n_argv[1] = command;
off = 2;
/* Use linux32 in the path */
vfunc = execvp;
} else {
/* Directly call lsi_exec_bin */
exec_command = command;
n_argv[0] = command;
/* Full path here due to shadow nature unless asked not to */
vfunc = use_path ? execvp : execv;
}
/* Point arguments to arguments passed to us */
for (i = 0; i < argc; i++) {
n_argv[i + off] = argv[i];
}
n_argv[i + 1 + off] = NULL;
/* Go execute steam. */
if (vfunc(exec_command, (char **)n_argv) < 0) {
lsi_report_failure("Failed to launch command: %s\n\n%s", command, strerror(errno));
return EXIT_FAILURE;
}
/* Can't happen */
return EXIT_FAILURE;
}
int shim_execute(const char *command, int argc, char **argv)
{
return shim_execute_internal(command, argc, argv, false);
}
int shim_execute_path(const char *command, int argc, char **argv)
{
return shim_execute_internal(command, argc, argv, true);
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=8 tabstop=8 expandtab:
* :indentSize=8:tabSize=8:noTabs=true:
*/