Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEXInterpreter: Support portable installs #3929

Merged
merged 1 commit into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions FEXCore/Scripts/config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 33 additions & 18 deletions Source/Common/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -391,15 +394,19 @@ 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");
if (SteamID) {
// 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));
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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");
Sonicadvance1 marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -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
16 changes: 11 additions & 5 deletions Source/Common/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXConfig/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXGetConfig/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
61 changes: 52 additions & 9 deletions Source/Tools/FEXLoader/FEXLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Sonicadvance1 marked this conversation as resolved.
Show resolved Hide resolved

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

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXQonfig/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXRootFSFetcher/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXServer/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/ARM64EC/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/WOW64/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading