From 8a2d262f40edaf2fb4cded71e6f67ffc82d34da6 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Wed, 7 Aug 2024 14:36:45 -0700 Subject: [PATCH] FEXInterpreter: Support portable installs --- FEXCore/Scripts/config_generator.py | 14 ++++++++ Source/Common/Config.cpp | 51 ++++++++++++++++++---------- Source/Common/Config.h | 15 +++++--- Source/Tools/FEXLoader/FEXLoader.cpp | 46 ++++++++++++++++++++++--- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/FEXCore/Scripts/config_generator.py b/FEXCore/Scripts/config_generator.py index 6d8488eb88..b80383a016 100644 --- a/FEXCore/Scripts/config_generator.py +++ b/FEXCore/Scripts/config_generator.py @@ -217,6 +217,20 @@ def print_man_environment_tail(): ], "''", True) + print_man_env_option( + "FEX_PORTABLE", + [ + "Forces FEX in to a more portable installation", + "This does a couple of things:", + "- Skips executing binfmt_misc handlers through execve", + "- global config (typically installed in /usr/share/fex-emu/) disabled", + "- FEX_APP_CONFIG_LOCATION defaults to FEXInterpreter relative paths instead of {$HOME, $XDG_DATA_HOME}/.fex-emu/", + " - /fex-emu/" + "- FEX_APP_DATA_LOCATION defaults to FEXInterpreter relative path instead of {$HOME, $XDG_DATA_HOME}/.fex-emu/", + " - /fex-emu/" + ], + "''", True) + def print_man_header(): header ='''.Dd {0} .Dt FEX diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp index cb4c9240cb..e11ac25c0f 100644 --- a/Source/Common/Config.cpp +++ b/Source/Common/Config.cpp @@ -339,10 +339,13 @@ fextl::string RecoverGuestProgramFilename(fextl::string Program, bool ExecFDInte } ApplicationNames LoadConfig(fextl::unique_ptr ArgsLoader, bool LoadProgramConfig, char** const envp, - bool ExecFDInterp, int ProgramFDFromEnv) { - FEX::Config::InitializeConfigs(); + bool ExecFDInterp, int ProgramFDFromEnv, const PortableInformation* PortableInfo) { + const bool IsPortable = PortableInfo && PortableInfo->IsPortable; + FEX::Config::InitializeConfigs(PortableInfo); FEXCore::Config::Initialize(); - FEXCore::Config::AddLayer(CreateGlobalMainLayer()); + if (!IsPortable) { + FEXCore::Config::AddLayer(CreateGlobalMainLayer()); + } FEXCore::Config::AddLayer(CreateMainLayer()); auto Args = ArgsLoader->Get(); @@ -392,7 +395,9 @@ ApplicationNames LoadConfig(fextl::unique_ptr ArgsLoa } } - FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_GLOBAL_APP)); + if (!IsPortable) { + FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_GLOBAL_APP)); + } FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_LOCAL_APP)); auto SteamID = getenv("SteamAppId"); @@ -400,7 +405,9 @@ ApplicationNames LoadConfig(fextl::unique_ptr ArgsLoa // If a SteamID exists then let's search for Steam application configs as well. // We want to key off both the SteamAppId number /and/ the executable since we may not want to thunk all binaries. fextl::string SteamAppName = fextl::fmt::format("Steam_{}_{}", SteamID, ProgramName); - FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_GLOBAL_STEAM_APP)); + if (!IsPortable) { + FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_GLOBAL_STEAM_APP)); + } FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_LOCAL_STEAM_APP)); } } @@ -474,12 +481,16 @@ const char* GetHomeDirectory() { } #endif -fextl::string GetDataDirectory() { - fextl::string DataDir {}; +fextl::string GetDataDirectory(const PortableInformation* PortableInfo) { + const char* DataOverride = getenv("FEX_APP_DATA_LOCATION"); + if (PortableInfo && PortableInfo->IsPortable && !DataOverride) { + return fextl::fmt::format("{}fex-emu/", PortableInfo->InterpreterPath); + } + + fextl::string DataDir {}; const char* HomeDir = GetHomeDirectory(); const char* DataXDG = getenv("XDG_DATA_HOME"); - const char* DataOverride = getenv("FEX_APP_DATA_LOCATION"); if (DataOverride) { // Data override will override the complete directory DataDir = DataOverride; @@ -490,14 +501,18 @@ fextl::string GetDataDirectory() { return DataDir; } -fextl::string GetConfigDirectory(bool Global) { +fextl::string GetConfigDirectory(bool Global, const PortableInformation* PortableInfo) { + const char* ConfigOverride = getenv("FEX_APP_CONFIG_LOCATION"); + if (PortableInfo && PortableInfo->IsPortable && (Global || (!Global && !ConfigOverride))) { + return fextl::fmt::format("{}fex-emu/", PortableInfo->InterpreterPath); + } + fextl::string ConfigDir; if (Global) { ConfigDir = GLOBAL_DATA_DIRECTORY; } else { const char* HomeDir = GetHomeDirectory(); const char* ConfigXDG = getenv("XDG_CONFIG_HOME"); - const char* ConfigOverride = getenv("FEX_APP_CONFIG_LOCATION"); if (ConfigOverride) { // Config override completely overrides the config directory ConfigDir = ConfigOverride; @@ -516,15 +531,15 @@ fextl::string GetConfigDirectory(bool Global) { return ConfigDir; } -fextl::string GetConfigFileLocation(bool Global) { - return GetConfigDirectory(Global) + "Config.json"; +fextl::string GetConfigFileLocation(bool Global, const PortableInformation* PortableInfo) { + return GetConfigDirectory(Global, PortableInfo) + "Config.json"; } -void InitializeConfigs() { - FEXCore::Config::SetDataDirectory(GetDataDirectory()); - FEXCore::Config::SetConfigDirectory(GetConfigDirectory(false), false); - FEXCore::Config::SetConfigDirectory(GetConfigDirectory(true), true); - FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(false), false); - FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(true), true); +void InitializeConfigs(const PortableInformation* PortableInfo) { + FEXCore::Config::SetDataDirectory(GetDataDirectory(PortableInfo)); + FEXCore::Config::SetConfigDirectory(GetConfigDirectory(false, PortableInfo), false); + FEXCore::Config::SetConfigDirectory(GetConfigDirectory(true, PortableInfo), true); + FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(false, PortableInfo), false); + FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(true, PortableInfo), true); } } // namespace FEX::Config diff --git a/Source/Common/Config.h b/Source/Common/Config.h index 880f072572..b9a8b8d04c 100644 --- a/Source/Common/Config.h +++ b/Source/Common/Config.h @@ -28,6 +28,11 @@ struct ApplicationNames { fextl::string ProgramName; }; +struct PortableInformation { + bool IsPortable; + std::string_view InterpreterPath; +}; + /** * @brief Loads the FEX and application configurations for the application that is getting ready to run. * @@ -40,15 +45,15 @@ struct ApplicationNames { * @return The application name and path structure */ ApplicationNames LoadConfig(fextl::unique_ptr ArgLoader, bool LoadProgramConfig, char** const envp, - bool ExecFDInterp, int ProgramFDFromEnv); + bool ExecFDInterp, int ProgramFDFromEnv, const PortableInformation* PortableInfo = nullptr); const char* GetHomeDirectory(); -fextl::string GetDataDirectory(); -fextl::string GetConfigDirectory(bool Global); -fextl::string GetConfigFileLocation(bool Global); +fextl::string GetDataDirectory(const PortableInformation* PortableInfo = nullptr); +fextl::string GetConfigDirectory(bool Global, const PortableInformation* PortableInfo = nullptr); +fextl::string GetConfigFileLocation(bool Global, const PortableInformation* PortableInfo = nullptr); -void InitializeConfigs(); +void InitializeConfigs(const PortableInformation* PortableInfo = nullptr); /** * @brief Loads the global FEX config diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index c3612824f2..250ea6694f 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -194,15 +194,49 @@ void RootFSRedirect(fextl::string* Filename, const fextl::string& RootFS) { } } +FEX::Config::PortableInformation GetIsPortable() { + constexpr FEX::Config::PortableInformation BadResult {false, {}}; + const char* PortableConfig = getenv("FEX_PORTABLE"); + if (!PortableConfig) { + return BadResult; + } + + uint32_t Value {}; + std::string_view PortableView {PortableConfig}; + + if (std::from_chars(PortableView.data(), PortableView.data() + PortableView.size(), Value).ec != std::errc {} || Value == 0) { + return BadResult; + } + + char SelfPath[PATH_MAX]; + auto Result = readlink("/proc/self/exe", SelfPath, PATH_MAX); + if (Result == -1) { + return BadResult; + } + + std::string_view SelfPathView {SelfPath, std::min(PATH_MAX, Result)}; + + ///< Extract the absolute path from the FEXInterpret path + return {true, SelfPathView.substr(0, SelfPathView.find_last_of("/") + 1)}; +} + bool RanAsInterpreter(const char* Program) { return ExecutedWithFD || FEXLOADER_AS_INTERPRETER; } +static bool InterpreterInstalled {}; + bool IsInterpreterInstalled() { - // The interpreter is installed if both the binfmt_misc handlers are available - // Or if we were originally executed with FD. Which means the interpreter is installed + return InterpreterInstalled; +} - return ExecutedWithFD || (access("/proc/sys/fs/binfmt_misc/FEX-x86", F_OK) == 0 && access("/proc/sys/fs/binfmt_misc/FEX-x86_64", F_OK) == 0); +void SetInterpreterInstalled(bool Portable) { + // The interpreter is installed if both the binfmt_misc handlers are available. + // Or if we were originally executed with FD. Which means the interpreter is installed. + // If portable is set then FEX ignores anything installed through binfmt_misc. + InterpreterInstalled = + !Portable && + (ExecutedWithFD || (access("/proc/sys/fs/binfmt_misc/FEX-x86", F_OK) == 0 && access("/proc/sys/fs/binfmt_misc/FEX-x86_64", F_OK) == 0)); } namespace FEX::TSO { @@ -267,6 +301,10 @@ static int StealFEXFDFromEnv(const char* Env) { int main(int argc, char** argv, char** const envp) { auto SBRKPointer = FEXCore::Allocator::DisableSBRKAllocations(); FEXCore::Allocator::GLIBCScopedFault GLIBFaultScope; + + const auto PortableInfo = GetIsPortable(); + SetInterpreterInstalled(PortableInfo.IsPortable); + const bool IsInterpreter = RanAsInterpreter(argv[0]); ExecutedWithFD = getauxval(AT_EXECFD) != 0; @@ -280,7 +318,7 @@ int main(int argc, char** argv, char** const envp) { argc, argv); auto Args = ArgsLoader->Get(); auto ParsedArgs = ArgsLoader->GetParsedArgs(); - auto Program = FEX::Config::LoadConfig(std::move(ArgsLoader), true, envp, ExecutedWithFD, FEXFD); + auto Program = FEX::Config::LoadConfig(std::move(ArgsLoader), true, envp, ExecutedWithFD, FEXFD, &PortableInfo); if (Program.ProgramPath.empty() && FEXFD == -1) { // Early exit if we weren't passed an argument