Files
FEX-Emu--FEX/Source/Steam/ServerManager.cpp
T
Ryan Houdek cdbf5d57bd Steam: Adds FEXServerManager
This is fairly simple. Needs to be installed alongside FEXServer, so
that it can start it in a portable config.

- Starts a FEXServer
- Tells pressure-vessel when FEXServer is ready
- Keeps FEXServer alive with the `watch_fd` as long as the process lives
- Listens for pressure-vessel to be shutting down
- Exits once pressure-vessel exits, also letting FEXServer shutdown if
  no FEX instances are alive.
2025-12-04 14:06:17 -08:00

97 lines
2.5 KiB
C++

// SPDX-License-Identifier: MIT
#include "PortabilityInfo.h"
#include "Common/FEXServerClient.h"
#include <cstdio>
#include <unistd.h>
#include <poll.h>
void MsgHandler(LogMan::DebugLevels Level, const char* Message) {
const auto Style = fmt::text_style {};
const auto Output = fextl::fmt::format("{} {}\n", fmt::styled(LogMan::DebugLevelStr(Level), Style), Message);
write(STDERR_FILENO, Output.c_str(), Output.size());
fsync(STDERR_FILENO);
}
void AssertHandler(const char* Message) {
return MsgHandler(LogMan::ASSERT, Message);
}
void SignalPVToContinue() {
// Tell pressure-vessel that the startup was a success.
const auto ReadyMsg = "READY=1\n";
write(STDOUT_FILENO, ReadyMsg, strlen(ReadyMsg));
// pressure-vessel is waiting for EOF on STDOUT from this process to ensure it can run FEX processes.
// dup2 atomically replaces stdout with a copy of stderr to achieve this.
dup2(STDERR_FILENO, STDOUT_FILENO);
}
struct PipesType {
int read_pipe {-1};
int write_pipe {-1};
};
PipesType get_pipe() {
PipesType pipes {};
pipe(&pipes.read_pipe);
return pipes;
}
int main(int argc, const char** argv, char** const envp) {
LogMan::Throw::InstallHandler(AssertHandler);
LogMan::Msg::InstallHandler(MsgHandler);
const auto PortableInfo = FEX::ReadPortabilityInformation();
FEX::Config::LoadConfig({}, envp, PortableInfo);
// Reload the meta layer
FEXCore::Config::ReloadMetaLayer();
auto pipes = get_pipe();
// Set the write side to close on exec.
fcntl(pipes.write_pipe, F_SETFD, FD_CLOEXEC);
// Give the read end of the pipe to FEXServer.
auto ServerFD = FEXServerClient::StartServer(PortableInfo.InterpreterPath, pipes.read_pipe);
if (ServerFD == -1) {
perror("Couldn't start FEXServer");
return 126;
}
// FEXServer is now running. Tell PV to continue.
SignalPVToContinue();
// Don't need the read pipe anymore.
close(pipes.read_pipe);
pipes.read_pipe = -1;
// Now that the server is started and watching our pipe, we can close the returned FD, as it'll stay open as long as the pipe is open.
close(ServerFD);
ServerFD = -1;
// stdin will be a pipe, so wait until that FD is closed.
while (true) {
pollfd p {
.fd = STDIN_FILENO,
.events = POLLRDHUP,
.revents = 0,
};
int events = poll(&p, 1, -1);
if (events == -1 && errno == EINTR) {
continue;
}
if (events > 0 && (p.revents & (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL))) {
// Error or pressure-vessel hung-up.
break;
}
}
// Terminating will clean-up.
return 0;
}