mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-08 17:00:18 +02:00
Fixes #2136 This is a fairly tricky edge case to support with FEX. If execveat is used with AT_EMPTY_PATH then the application can pass an FD to execve instead of a filename. This includes FDs that have been deleted from the disk so the child process can't open it by filename anymore. To work around this limitation, we need to pass the FD to the new FEX process and open it directly, similar to how binfmt_misc works with FDs. The FD will get passed through environment variables, which the new process will check for and then remove the variable from the environment. Lots of prickly edge cases to support here. Without binfmt_misc: - Passes the FD to FEXLoader directly. - Requires duplicating the FD if it has O_CLOEXEC on the FD. With binfmt_misc: - Shebang file, pass directly to FEXLoader, just like without binfmt. - x86 ELF Files, rely on the kernel's binfmt_misc support here. - Unsupported ELF files, let kernel handle it through binfmt_misc Argument handling: - The application can pass in no arguments. - Means our application configurations were failing to find a config - Also various checks in the frontend were failing. - If opened through an FD, find the symlink for that FD for the application configuration instead. Side note: Fixed a performance issue in execve where when we were checking for file format support. Either ELF or Shebang files, we were reading the /whole/ file upfront. We only need to read a header worth of ELF files, and only 257 bytes if it is potentially a shebang file. Should dramatically reduce some application's execve times.
236 lines
6.2 KiB
C++
236 lines
6.2 KiB
C++
#include "ArgumentLoader.h"
|
|
#include "Logger.h"
|
|
#include "PipeScanner.h"
|
|
#include "ProcessPipe.h"
|
|
#include "SquashFS.h"
|
|
#include "Common/Config.h"
|
|
#include "Common/FEXServerClient.h"
|
|
|
|
#include <chrono>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <filesystem>
|
|
#include <iterator>
|
|
#include <mutex>
|
|
#include <poll.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/signal.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <sys/wait.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
namespace Logging {
|
|
const char *GetCharLevel(uint32_t Level) {
|
|
switch (Level) {
|
|
case LogMan::NONE:
|
|
return "NONE";
|
|
break;
|
|
case LogMan::ASSERT:
|
|
return "ASSERT";
|
|
break;
|
|
case LogMan::ERROR:
|
|
return "ERROR";
|
|
break;
|
|
case LogMan::DEBUG:
|
|
return "DEBUG";
|
|
break;
|
|
case LogMan::INFO:
|
|
return "Info";
|
|
break;
|
|
case LogMan::STDOUT:
|
|
return "STDOUT";
|
|
break;
|
|
case LogMan::STDERR:
|
|
return "STDERR";
|
|
break;
|
|
default:
|
|
return "???";
|
|
break;
|
|
}
|
|
}
|
|
void MsgHandler(LogMan::DebugLevels Level, char const *Message) {
|
|
const auto Output = fmt::format("[{}] {}\n", GetCharLevel(Level), Message);
|
|
write(STDOUT_FILENO, Output.c_str(), Output.size());
|
|
}
|
|
|
|
void AssertHandler(char const *Message) {
|
|
const auto Output = fmt::format("[ASSERT] {}\n", Message);
|
|
write(STDOUT_FILENO, Output.c_str(), Output.size());
|
|
}
|
|
|
|
void ClientMsgHandler(int FD, uint64_t Timestamp, uint32_t PID, uint32_t TID, uint32_t Level, const char* Msg) {
|
|
auto CharLevel = GetCharLevel(Level);
|
|
const auto Output = fmt::format("[{}][{}][{}.{}] {}\n", CharLevel, Timestamp, PID, TID, Msg);
|
|
write(STDERR_FILENO, Output.c_str(), Output.size());
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
void ActionHandler(int sig, siginfo_t *info, void *context) {
|
|
// FEX_TODO("Fix this");
|
|
if (sig == SIGINT) {
|
|
// Someone trying to kill us. Shutdown.
|
|
ProcessPipe::Shutdown();
|
|
return;
|
|
}
|
|
_exit(1);
|
|
}
|
|
|
|
void ActionIgnore(int sig, siginfo_t *info, void *context) {
|
|
}
|
|
|
|
void SetupSignals() {
|
|
// Setup our signal handlers now so we can capture some events
|
|
struct sigaction act{};
|
|
act.sa_sigaction = ActionHandler;
|
|
act.sa_flags = SA_SIGINFO;
|
|
|
|
// SIGTERM if something is trying to terminate us
|
|
sigaction(SIGTERM, &act, nullptr);
|
|
// SIGINT if something is trying to terminate us
|
|
sigaction(SIGINT, &act, nullptr);
|
|
|
|
// SIGUSR1 just to interrupt syscalls
|
|
act.sa_sigaction = ActionIgnore;
|
|
sigaction(SIGUSR1, &act, nullptr);
|
|
|
|
// Ignore SIGPIPE, we will be checking for pipe closure which could send this signal
|
|
signal(SIGPIPE, SIG_IGN);
|
|
// SIGCHLD if squashfuse exits early.
|
|
// Ignore it for now
|
|
signal(SIGCHLD, SIG_IGN);
|
|
}
|
|
|
|
/**
|
|
* @brief Deparents itself by forking and terminating the parent process.
|
|
*/
|
|
void DeparentSelf() {
|
|
auto SystemdEnv = getenv("SYSTEMD_EXEC_PID");
|
|
if (SystemdEnv) {
|
|
// If FEXServer was launched through systemd then don't deparent, otherwise systemd kills the entire server.
|
|
return;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
|
|
if (pid != 0) {
|
|
// Parent is leaving to force this process to deparent itself
|
|
// This lets this process become the child of whatever the reaper parent is
|
|
_exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv, char **const envp) {
|
|
auto Options = FEXServer::Config::Load(argc, argv);
|
|
|
|
SetupSignals();
|
|
|
|
if (Options.Foreground) {
|
|
LogMan::Throw::InstallHandler(Logging::AssertHandler);
|
|
LogMan::Msg::InstallHandler(Logging::MsgHandler);
|
|
}
|
|
|
|
// Scan for any incoming pipes
|
|
// We will close these later
|
|
PipeScanner::ScanForPipes();
|
|
|
|
if (!Options.Foreground) {
|
|
DeparentSelf();
|
|
}
|
|
|
|
FEX::Config::LoadConfig(
|
|
true,
|
|
false,
|
|
argc, argv, envp,
|
|
false, {}
|
|
);
|
|
|
|
// Reload the meta layer
|
|
FEXCore::Config::ReloadMetaLayer();
|
|
|
|
if (Options.Wait) {
|
|
int ServerPipe = FEXServerClient::ConnectToServer();
|
|
if (ServerPipe != -1) {
|
|
int FEXServerPID = FEXServerClient::RequestPIDFD(ServerPipe);
|
|
close(ServerPipe);
|
|
if (FEXServerPID != -1) {
|
|
LogMan::Msg::IFmt("[FEXServer] Waiting for FEXServer to close");
|
|
// We can't use waitid (P_PIDFD) here because the active FEXServer isn't a child of this process.
|
|
// Use poll instead which will return once the pidfd closes.
|
|
pollfd PollFD;
|
|
PollFD.fd = FEXServerPID;
|
|
PollFD.events = POLLIN | POLLOUT | POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
|
|
|
|
// Wait for a result on the pipe that isn't EINTR
|
|
while (poll(&PollFD, 1, -1) == -1 && errno == EINTR);
|
|
|
|
LogMan::Msg::IFmt("[FEXServer] FEXServer shutdown");
|
|
}
|
|
PipeScanner::ClosePipes();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (Options.Kill) {
|
|
int ServerPipe = FEXServerClient::ConnectToServer();
|
|
|
|
if (ServerPipe != -1) {
|
|
FEXServerClient::RequestServerKill(ServerPipe);
|
|
LogMan::Msg::DFmt("[FEXServer] Sent kill packet");
|
|
PipeScanner::ClosePipes();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (!ProcessPipe::InitializeServerPipe()) {
|
|
// Someone else already owns the FEXServer pipe
|
|
PipeScanner::ClosePipes();
|
|
return -1;
|
|
}
|
|
|
|
if (!ProcessPipe::InitializeServerSocket()) {
|
|
// Couldn't create server socket for some reason
|
|
PipeScanner::ClosePipes();
|
|
return -1;
|
|
}
|
|
|
|
// Switch this process over to a new session id
|
|
// Probably not required but allows this to become the process group leader of its session
|
|
::setsid();
|
|
|
|
// Set process as a subreaper so subprocesses can't escape
|
|
::prctl(PR_SET_CHILD_SUBREAPER, 1);
|
|
|
|
if (Options.Foreground) {
|
|
// Only start a log thread if we are in the foreground.
|
|
// Prevents FEXInterpreter from trying to log to nothing.
|
|
Logger::StartLogThread();
|
|
}
|
|
|
|
if (!SquashFS::InitializeSquashFS()) {
|
|
LogMan::Msg::DFmt("[FEXServer] Couldn't mount squashfs");
|
|
return -1;
|
|
}
|
|
|
|
// Close the pipes we found at the start
|
|
// This will let FEXInterpreter know we are ready
|
|
PipeScanner::ClosePipes();
|
|
|
|
ProcessPipe::SetConfiguration(Options.Foreground, Options.PersistentTimeout ?: 10);
|
|
|
|
// Actually spin up the request thread.
|
|
// Any applications that were waiting for the socket to accept will then go through here.
|
|
ProcessPipe::WaitForRequests();
|
|
|
|
SquashFS::UnmountRootFS();
|
|
|
|
Logger::Shutdown();
|
|
|
|
return 0;
|
|
}
|