Merge pull request #3929 from Sonicadvance1/portable

FEXInterpreter: Support portable installs
This commit is contained in:
Ryan Houdek 2024-09-02 10:13:07 -07:00 committed by GitHub
commit 9336e35052
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 112 additions and 40 deletions

View File

@ -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 <FEXInterpreterPath>/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

View File

@ -338,10 +338,13 @@ fextl::string RecoverGuestProgramFilename(fextl::string Program, bool ExecFDInte
}
ApplicationNames LoadConfig(fextl::unique_ptr<FEX::ArgLoader::ArgLoader> 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<FEX::ArgLoader::ArgLoader> 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<FEX::ArgLoader::ArgLoader> 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

View File

@ -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<FEX::ArgLoader::ArgLoader> 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

View File

@ -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);

View File

@ -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());

View File

@ -64,7 +64,6 @@ $end_info$
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<size_t>(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");

View File

@ -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);

View File

@ -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>(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();

View File

@ -117,7 +117,7 @@ int main(int argc, char** argv, char** const envp) {
}
auto ArgsLoader = fextl::make_unique<FEX::ArgLoader::ArgLoader>(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();

View File

@ -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>(FEX::ArgLoader::ArgLoader::LoadType::WITH_FEXLOADER_PARSER, argc, argv);
auto Args = ArgsLoader->Get();

View File

@ -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());

View File

@ -424,7 +424,7 @@ public:
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());