From c413d7950b88cb39fb7c6350c69ab02d814d0885 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 | 8 +++ Source/Common/Config.cpp | 51 ++++++++++------ Source/Common/Config.h | 16 +++-- Source/Tools/FEXConfig/Main.cpp | 2 +- Source/Tools/FEXGetConfig/Main.cpp | 2 +- Source/Tools/FEXLoader/FEXLoader.cpp | 61 ++++++++++++++++--- Source/Tools/FEXQonfig/Main.cpp | 2 +- Source/Tools/FEXRootFSFetcher/Main.cpp | 2 +- Source/Tools/FEXServer/Main.cpp | 2 +- .../TestHarnessRunner/TestHarnessRunner.cpp | 2 +- Source/Windows/ARM64EC/Module.cpp | 2 +- Source/Windows/WOW64/Module.cpp | 2 +- 12 files changed, 112 insertions(+), 40 deletions(-) diff --git a/FEXCore/Scripts/config_generator.py b/FEXCore/Scripts/config_generator.py index 6d8488eb88..28828078f6 100644 --- a/FEXCore/Scripts/config_generator.py +++ b/FEXCore/Scripts/config_generator.py @@ -217,6 +217,14 @@ def print_man_environment_tail(): ], "''", True) + print_man_env_option( + "FEX_PORTABLE", + [ + "Allows FEX to run without installation. Global locations for configuration and binfmt_misc are ignored. These files are instead read from /fex-emu/ by default.", + "For further customization, see FEX_APP_CONFIG_LOCATION and FEX_APP_DATA_LOCATION." + ], + "''", True) + def print_man_header(): header ='''.Dd {0} .Dt FEX diff --git a/Source/Common/Config.cpp b/Source/Common/Config.cpp index 9dba05a8d6..3281ab9312 100644 --- a/Source/Common/Config.cpp +++ b/Source/Common/Config.cpp @@ -338,10 +338,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.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(); @@ -391,7 +394,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"); @@ -399,7 +404,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)); } } @@ -473,12 +480,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.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; @@ -489,14 +500,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.IsPortable && (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; @@ -515,15 +530,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..fe3a0308c0 100644 --- a/Source/Common/Config.h +++ b/Source/Common/Config.h @@ -28,6 +28,12 @@ struct ApplicationNames { fextl::string ProgramName; }; +struct PortableInformation { + bool IsPortable; + // Path of folder containing FEXInterpreter (including / at the end) + fextl::string InterpreterPath; +}; + /** * @brief Loads the FEX and application configurations for the application that is getting ready to run. * @@ -40,15 +46,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); const char* GetHomeDirectory(); -fextl::string GetDataDirectory(); -fextl::string GetConfigDirectory(bool Global); -fextl::string GetConfigFileLocation(bool Global); +fextl::string GetDataDirectory(const PortableInformation& PortableInfo); +fextl::string GetConfigDirectory(bool Global, const PortableInformation& PortableInfo); +fextl::string GetConfigFileLocation(bool Global, const PortableInformation& PortableInfo); -void InitializeConfigs(); +void InitializeConfigs(const PortableInformation& PortableInfo); /** * @brief Loads the global FEX config diff --git a/Source/Tools/FEXConfig/Main.cpp b/Source/Tools/FEXConfig/Main.cpp index 79922be48f..2dcfced96f 100644 --- a/Source/Tools/FEXConfig/Main.cpp +++ b/Source/Tools/FEXConfig/Main.cpp @@ -992,7 +992,7 @@ bool DrawUI() { } // namespace int main(int argc, char** argv) { - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); fextl::string ImGUIConfig = FEXCore::Config::GetConfigDirectory(false) + "FEXConfig_imgui.ini"; auto [window, gl_context] = FEX::GUI::SetupIMGui("#FEXConfig", ImGUIConfig); diff --git a/Source/Tools/FEXGetConfig/Main.cpp b/Source/Tools/FEXGetConfig/Main.cpp index cf15212186..373c03225f 100644 --- a/Source/Tools/FEXGetConfig/Main.cpp +++ b/Source/Tools/FEXGetConfig/Main.cpp @@ -107,7 +107,7 @@ TSOEmulationFacts GetTSOEmulationFacts() { } // namespace int main(int argc, char** argv, char** envp) { - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer()); diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index d16844ef42..347387bace 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -64,7 +64,6 @@ desc: Glues the ELF loader, FEXCore and LinuxSyscalls to launch an elf under fex namespace { static bool SilentLog; static int OutputFD {-1}; -static bool ExecutedWithFD {false}; void MsgHandler(LogMan::DebugLevels Level, const char* Message) { if (SilentLog) { @@ -194,14 +193,55 @@ void RootFSRedirect(fextl::string* Filename, const fextl::string& RootFS) { } } -bool RanAsInterpreter(const char* Program) { +FEX::Config::PortableInformation ReadPortabilityInformation() { + const 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; + } + + // Read the FEXInterpreter path from `/proc/self/exe` which is always a symlink to the absolute path of the executable running. + // This way we can get the parent path that the application is executing from. + 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 FEXInterpreter path + return {true, fextl::string {SelfPathView.substr(0, SelfPathView.find_last_of('/') + 1)}}; +} + +bool RanAsInterpreter(bool ExecutedWithFD) { return ExecutedWithFD || FEXLOADER_AS_INTERPRETER; } -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 +/** + * @brief Queries if FEX is installed as a binfmt_misc interpreter + * + * @param ExecutedWithFD If FEXInterpreter was executed using a binfmt_misc FD handle from the kernel + * @param Portable Portability information about FEX being run in portable mode + * + * @return true if the binfmt_misc handlers are installed and being used + */ +bool QueryInterpreterInstalled(bool ExecutedWithFD, const FEX::Config::PortableInformation& Portable) { + if (Portable.IsPortable) { + // Don't use binfmt interpreter even if it's installed + return false; + } + // Check if FEX's binfmt_misc handlers are both installed. + // The explicit check can be omitted if FEX was executed from an FD, + // since this only happens if the kernel launched FEX through binfmt_misc 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); } @@ -267,9 +307,12 @@ 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 bool IsInterpreter = RanAsInterpreter(argv[0]); - ExecutedWithFD = getauxval(AT_EXECFD) != 0; + const bool ExecutedWithFD = getauxval(AT_EXECFD) != 0; + const bool IsInterpreter = RanAsInterpreter(ExecutedWithFD); + const auto PortableInfo = ReadPortabilityInformation(); + const bool InterpreterInstalled = QueryInterpreterInstalled(ExecutedWithFD, PortableInfo); + int FEXFD {StealFEXFDFromEnv("FEX_EXECVEFD")}; LogMan::Throw::InstallHandler(AssertHandler); @@ -280,7 +323,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 @@ -290,7 +333,7 @@ int main(int argc, char** argv, char** const envp) { // Reload the meta layer FEXCore::Config::ReloadMetaLayer(); FEXCore::Config::Set(FEXCore::Config::CONFIG_IS_INTERPRETER, IsInterpreter ? "1" : "0"); - FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, IsInterpreterInstalled() ? "1" : "0"); + FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, InterpreterInstalled ? "1" : "0"); #ifdef VIXL_SIMULATOR // If running under the vixl simulator, ensure that indirect runtime calls are enabled. FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_DISABLE_VIXL_INDIRECT_RUNTIME_CALLS, "0"); diff --git a/Source/Tools/FEXQonfig/Main.cpp b/Source/Tools/FEXQonfig/Main.cpp index 008991431b..a7ab856df8 100644 --- a/Source/Tools/FEXQonfig/Main.cpp +++ b/Source/Tools/FEXQonfig/Main.cpp @@ -351,7 +351,7 @@ void ConfigRuntime::onLoad(const QUrl& Filename) { int main(int Argc, char** Argv) { QApplication App(Argc, Argv); - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); fextl::string ConfigFilename = Argc > 1 ? Argv[1] : FEXCore::Config::GetConfigFileLocation(); ConfigInit(ConfigFilename); diff --git a/Source/Tools/FEXRootFSFetcher/Main.cpp b/Source/Tools/FEXRootFSFetcher/Main.cpp index b154931927..9e773ee339 100644 --- a/Source/Tools/FEXRootFSFetcher/Main.cpp +++ b/Source/Tools/FEXRootFSFetcher/Main.cpp @@ -1091,7 +1091,7 @@ bool ExtractEroFS(const fextl::string& Path, const fextl::string& RootFS, const int main(int argc, char** argv, char** const envp) { auto ArgsLoader = fextl::make_unique(FEX::ArgLoader::ArgLoader::LoadType::WITHOUT_FEXLOADER_PARSER, argc, argv); - FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, {}); + FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, false, FEX::Config::PortableInformation {}); // Reload the meta layer FEXCore::Config::ReloadMetaLayer(); diff --git a/Source/Tools/FEXServer/Main.cpp b/Source/Tools/FEXServer/Main.cpp index 4b211a7c4d..a2a8ced9c7 100644 --- a/Source/Tools/FEXServer/Main.cpp +++ b/Source/Tools/FEXServer/Main.cpp @@ -117,7 +117,7 @@ int main(int argc, char** argv, char** const envp) { } auto ArgsLoader = fextl::make_unique(FEX::ArgLoader::ArgLoader::LoadType::WITHOUT_FEXLOADER_PARSER, argc, argv); - FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, {}); + FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, false, FEX::Config::PortableInformation {}); // Reload the meta layer FEXCore::Config::ReloadMetaLayer(); diff --git a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp index 3804c30e41..cb8a6e8ce2 100644 --- a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp +++ b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp @@ -199,7 +199,7 @@ int main(int argc, char** argv, char** const envp) { LogMan::Throw::InstallHandler(AssertHandler); LogMan::Msg::InstallHandler(MsgHandler); - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); FEXCore::Config::Initialize(); auto ArgsLoader = fextl::make_unique(FEX::ArgLoader::ArgLoader::LoadType::WITH_FEXLOADER_PARSER, argc, argv); auto Args = ArgsLoader->Get(); diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index a8c792baa6..21118a4d83 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -456,7 +456,7 @@ extern "C" void SyncThreadContext(CONTEXT* Context) { NTSTATUS ProcessInit() { FEX::Windows::InitCRTProcess(); - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer()); diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 4c0cb902f6..ace687946b 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -424,7 +424,7 @@ class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::A void BTCpuProcessInit() { FEX::Windows::InitCRTProcess(); - FEX::Config::InitializeConfigs(); + FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {}); FEXCore::Config::Initialize(); FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer()); FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer());