Files
MobileGL/MobileGL/MG_Remote/Server/ServerMain.cpp
T

312 lines
16 KiB
C++

// MobileGL - separate-process server and TCP session supervisor.
// SPDX-License-Identifier: LGPL-3.0-only
#include "../Transport/Doorbell.h"
#include "../Transport/SocketTransport.h"
#include "../Transport/WireLog.h"
#include "../Protocol/SurfaceOpCodec.h"
#include "../Handshake.h"
#include "ServerLoop.h"
#include "ServerSession.h"
#include "SurfaceControlFrame.h"
#include <Config.h>
#include <Init.h>
#include <MG_Backend/MGPipe/PipeInputs.h>
#include <MG_Backend/ServerRole.h>
#include <MG_Util/Debug/Log.h>
#include <MG_Util/Metrics/PipeStats.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <chrono>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
namespace {
using namespace MobileGL::MG_Remote;
using namespace MobileGL::MG_Remote::Transport;
namespace Protocol = ::MobileGL::Wire;
void Refuse(SocketTransport& transport, Protocol::RefuseCode code, const char* detail) {
flatbuffers::FlatBufferBuilder builder(256);
auto refusal = Protocol::CreateRefuseDirect(builder, code, detail);
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::Refuse, refusal.Union());
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()});
WireLogError("MG_Remote server: Refuse{%s} %s", Protocol::EnumNameRefuseCode(code), detail);
}
void ForwardLog(void* user, const char* text) {
auto& transport = *static_cast<ITransport*>(user);
flatbuffers::FlatBufferBuilder builder(512);
auto log = Protocol::CreateLogLine(builder, Protocol::LogLevel::Info, builder.CreateString(text));
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogLine, log.Union());
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
(void)transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()});
}
void RefuseBusy(SocketTransport& transport, const char* detail) {
// Unread Hello bytes can make close send RST and discard our Refuse.
std::uint64_t bytes = 0;
if (transport.ReceiveFrame({nullptr, 0}, &bytes, 2000) == MOBILEGL_ERR_BUFFER_TOO_SMALL &&
bytes <= 1024 * 1024) {
std::vector<std::uint8_t> hello(static_cast<std::size_t>(bytes));
(void)transport.ReceiveFrame({hello.data(), hello.size()}, &bytes, 0);
}
Refuse(transport, Protocol::RefuseCode::Busy, detail);
}
struct FlushAck { ITransport* transport; std::uint64_t seq; };
void SendLogAck(void* pointer) {
auto& ack = *static_cast<FlushAck*>(pointer);
flatbuffers::FlatBufferBuilder builder(64);
auto flush = Protocol::CreateLogFlush(builder, ack.seq, true);
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogFlush, flush.Union());
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
(void)ack.transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()});
}
// Called only after fork: the supervisor never creates a backend or EGL context.
[[noreturn]] void RunSession(std::unique_ptr<SocketTransport> control) {
const int selfPid = static_cast<int>(::getpid());
const bool tcp = control->IsTcp();
std::vector<std::uint8_t> helloFrame;
std::uint64_t bytes = 0;
auto received = control->ReceiveFrame({nullptr, 0}, &bytes, 10000);
if (received != MOBILEGL_ERR_BUFFER_TOO_SMALL || bytes > 1024 * 1024) ::_exit(67);
helloFrame.resize(static_cast<std::size_t>(bytes));
if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) ::_exit(67);
flatbuffers::Verifier verifier(helloFrame.data(), helloFrame.size());
if (!Protocol::VerifyCtrlEnvelopeBuffer(verifier)) ::_exit(67);
const auto* hello = Protocol::GetCtrlEnvelope(helloFrame.data())->msg_as_Hello();
if (!hello) ::_exit(67);
// Reject incompatible wire before backend bring-up can obscure the cause.
if (ValidatePeerHandshake(*control, hello->abiMajor(), hello->abiMinor(),
hello->wireFingerprint(), hello->buildFingerprint() ? hello->buildFingerprint()->c_str() : nullptr,
hello->dialMode()) != MOBILEGL_OK) ::_exit(0);
if (tcp) {
const char* expected = std::getenv("MOBILEGL_IPC_TOKEN");
const std::string token = hello->token() ? hello->token()->str() : std::string();
if (token != (expected ? expected : "")) {
Refuse(*control, Protocol::RefuseCode::Authentication, "TCP token mismatch");
::_exit(0);
}
}
MobileGL::MG_ConfigLoader::Init();
MobileGL::MG_Config::Transport = MobileGL::MG_Config::TransportMode::Spawn;
MobileGL::MG_Pipe::MGPipeSetServerProcessRole(true);
// The child bypasses MobileGL::Initialize: arm its own role counters.
MobileGL::MG_Util::PipeStats::Init();
const auto backend = static_cast<MobileGL::BackendType>(hello->backendType());
if (backend != MobileGL::BackendType::DirectGLES && backend != MobileGL::BackendType::DirectVulkan) {
Refuse(*control, Protocol::RefuseCode::Backend, "unsupported Hello.backendType");
::_exit(0);
}
const char* pinned = std::getenv("MOBILEGL_BACKEND_TYPE");
if (pinned && *pinned && backend != MobileGL::MG_Config::ActiveBackendType) {
Refuse(*control, Protocol::RefuseCode::Backend, "Hello.backendType disagrees with pinned backend");
::_exit(0);
}
MobileGL::MG_Config::ActiveBackendType = backend;
if (!MobileGL::MG_Backend::InitServerRoleForSpawn()) ::_exit(66);
auto& session = Server::ServerSessionInstance();
int serverBell[2] = {-1, -1}, clientBell[2] = {-1, -1};
std::unique_ptr<SocketDoorbell> selfBell, peerBell;
if (!tcp) {
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, serverBell) != 0 ||
::socketpair(AF_UNIX, SOCK_STREAM, 0, clientBell) != 0) ::_exit(74);
selfBell = std::make_unique<SocketDoorbell>(serverBell[0], serverBell[1], 1, true);
peerBell = std::make_unique<SocketDoorbell>(-1, clientBell[1], 1, true);
session.SetExternalDoorbells(selfBell.get(), peerBell.get());
}
const auto accepted = session.Accept(*control, &helloFrame);
if (accepted != MOBILEGL_OK) {
WireLogError("MG_Remote server: pid=%d Accept failed (rc=%d)", selfPid, static_cast<int>(accepted));
std::fflush(nullptr);
::_exit(accepted == MOBILEGL_ERR_PROTOCOL_MISMATCH ? 0 : 67);
}
if (!tcp)
{
struct Sideband {
std::uint32_t slot;
std::uint64_t bytes;
};
using Slot = SessionSegmentSlot;
static constexpr Slot kSlots[4] = {Slot::Cmd, Slot::Stage, Slot::Reply, Slot::Event};
for (const Slot slot : kSlots) {
const std::uint32_t tag = static_cast<std::uint32_t>(slot);
const int fd = session.Shm().DescriptorFor(slot);
if (fd < 0) {
std::fprintf(stderr, "MG_Remote server: pid=%d segment %u has no descriptor\n",
selfPid, tag);
std::fflush(nullptr);
::_exit(68);
}
Sideband sideband{tag, session.Shm().AnnouncedSize(slot)};
if (control->ShareFd(fd, MobileGLByteSpan{&sideband, sizeof(sideband)}) !=
MOBILEGL_OK) {
std::fprintf(stderr, "MG_Remote server: pid=%d could not share segment %u\n",
selfPid, tag);
std::fflush(nullptr);
::_exit(69);
}
}
// SLOTS 4, 5 AND 6 ARE THE BELLS - three, not two, and the third is the
// one the two-fd form above forces:
// 4 serverBell[1] the client's handle to RING THE SERVER
// 5 clientBell[0] the client's PARK end
// 6 clientBell[1] the client's handle to ring ITSELF, for the same
// reason the server needs serverBell[1]
// Sharing DUPs, so the descriptors these bells own stay valid here.
const int bellFds[3] = {serverBell[1], clientBell[0], clientBell[1]};
for (std::uint32_t index = 0; index < 3; ++index) {
Sideband sideband{4 + index, 0};
if (control->ShareFd(bellFds[index], MobileGLByteSpan{&sideband, sizeof(sideband)}) !=
MOBILEGL_OK) {
std::fprintf(stderr, "MG_Remote server: pid=%d could not share bell %u\n", selfPid,
index);
std::fflush(nullptr);
::_exit(69);
}
}
// clientBell[0] is the CLIENT's park end and no bell here owns it; the
// other three belong to selfBell and peerBell and are closed with them.
::close(clientBell[0]);
}
const char* forwarding = std::getenv("MOBILEGL_IPC_LOG_FORWARD");
if (tcp && (!forwarding || std::strcmp(forwarding, "0") != 0))
MobileGL::MG_Util::Debug::SetLogForwarder(&ForwardLog, control.get());
auto& loop = Server::ServerLoopInstance();
if (loop.Start(session) != MOBILEGL_OK) ::_exit(70);
WireLogError("MG_Remote server: pid=%d transport=spawn role=server ready control=%s data=%s",
selfPid, tcp ? "tcp" : "unix", tcp ? "stream" : "shm");
std::vector<std::uint8_t> buffer(64 * 1024);
for (;;) {
std::uint64_t size = 0;
const auto result = control->ReceiveFrame({buffer.data(), buffer.size()}, &size, kWaitForever);
if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL) { buffer.resize(static_cast<std::size_t>(size)); continue; }
if (result != MOBILEGL_OK) break;
flatbuffers::Verifier check(buffer.data(), static_cast<std::size_t>(size));
if (!Protocol::VerifyCtrlEnvelopeBuffer(check)) break;
const auto* envelope = Protocol::GetCtrlEnvelope(buffer.data());
if (const auto* flush = envelope->msg_as_LogFlush()) {
if (flush->ack()) break;
if (const char* inspect = std::getenv("MOBILEGL_TEST_SERVER_COUNTERS");
inspect && std::strcmp(inspect, "1") == 0) {
// SyncPeerLog has fenced the client's complete command prefix.
// Acquire the applier's publication before reading its counters;
// this serial client sends no more records until this ack.
const auto applied = session.DataLink()->Progress()->appliedSeq.load(std::memory_order_acquire);
const auto& verbs = session.Applier().Verbs();
MGLOG_I("MGPipe server counters: applied=%llu resets=%llu reset-serial=%llu deaths=%llu",
static_cast<unsigned long long>(applied),
static_cast<unsigned long long>(verbs.ApplierResets()),
static_cast<unsigned long long>(verbs.ExpectedApplierResetSerial()),
static_cast<unsigned long long>(verbs.ObjectDeaths()));
}
FlushAck ack{control.get(), flush->seq()};
MobileGL::MG_Util::Debug::WithLogBarrier(&SendLogAck, &ack);
continue;
}
const auto* operation = envelope->msg_as_SurfaceOp();
if (!operation) break;
Server::SurfaceControlFrame reply{};
(void)ServerApplyWireSurfaceOp(*operation, &reply);
session.FlushDataProgress();
if (session.DataLink()) reply.eventHead = session.DataLink()->EventPublishedHead();
flatbuffers::FlatBufferBuilder builder(256);
EncodeSurfaceReplyFrame(reply, &builder);
if (control->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) break;
}
loop.Stop();
// Publish the final server window while log forwarding is still attached.
MobileGL::MG_Util::PipeStats::Shutdown();
session.Close();
MobileGL::MG_Util::Debug::SetLogForwarder(nullptr, nullptr);
// _exit closes control after cleanup/flush; peer EOF is the client's fence.
std::fflush(nullptr);
::_exit(0);
}
}
extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int argc, char** argv) {
// The supervisor and every forked session child log under the server role.
// Set it here rather than trusting the launcher to have done so: log role is
// resolved from this env at first use, and a launcher that omits it would
// silently misfile this process's main-thread server lines as the client
// role and never forward them. Done before any logging so the value is cached
// as server and inherited by the fork children. DIAL/scrub stay hard checks.
if (const char* role = std::getenv("MOBILEGL_IPC_ROLE");
!role || std::strcmp(role, "server") != 0) {
::setenv("MOBILEGL_IPC_ROLE", "server", 1);
}
const char* dial = std::getenv("MOBILEGL_IPC_DIAL");
if (!dial || std::strcmp(dial, "no") != 0) {
std::fprintf(stderr, "MG_Remote server: MOBILEGL_IPC_DIAL=no anti-recursion catch (a) missing\n");
return 64;
}
for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB"}) {
if (std::getenv(name)) {
std::fprintf(stderr, "MG_Remote server: envp scrub FAILED, catch (b): %s\n", name);
return 65;
}
}
std::string endpoint;
bool serve = false;
for (int i = 1; i < argc; ++i) {
const std::string arg(argv[i]);
if (arg == "--serve") serve = true;
else if (arg == "--max-sessions" && i + 1 < argc) {
char* end = nullptr;
const auto count = std::strtoul(argv[++i], &end, 10);
// This is a concurrency limit, not a cumulative session budget.
// P6.5 deliberately supports one active rendering session.
if (*end || count != 1) return 71;
} else if (endpoint.empty() && arg.compare(0, 2, "--") != 0) endpoint = arg;
else return 71;
}
if (endpoint.empty()) if (const char* value = std::getenv("MOBILEGL_IPC_ENDPOINT")) endpoint = value;
if (endpoint.empty()) return 71;
int listener = -1;
if (SocketTransport::Listen(endpoint, &listener) != MOBILEGL_OK) return 72;
WireLogError("MG_Remote server: pid=%d listening on %s", static_cast<int>(::getpid()), endpoint.c_str());
pid_t active = -1;
for (;;) {
if (active > 0 && ::waitpid(active, nullptr, WNOHANG) == active) active = -1;
std::unique_ptr<SocketTransport> control;
const auto accepted = SocketTransport::AcceptPair(listener, serve ? 250 : 30000, control);
if (accepted == MOBILEGL_ERR_TIMEOUT && serve) continue;
if (accepted != MOBILEGL_OK) { ::close(listener); return 73; }
if (!serve) {
::close(listener);
if (endpoint[0] != '@' && endpoint.compare(0, 6, "tcp://") != 0) ::unlink(endpoint.c_str());
RunSession(std::move(control));
}
if (active > 0 && ::waitpid(active, nullptr, WNOHANG) == active) active = -1;
// exit_group closes files just before waitpid can observe the exit.
// Allow that small scheduling window; a live peer remains Busy.
const auto reapDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(50);
while (active > 0 && std::chrono::steady_clock::now() < reapDeadline) {
if (::waitpid(active, nullptr, WNOHANG) == active) { active = -1; break; }
::usleep(1000);
}
if (active > 0) {
RefuseBusy(*control, "one session is already active");
continue;
}
std::fflush(nullptr);
const pid_t child = ::fork();
if (child == 0) { ::close(listener); RunSession(std::move(control)); }
if (child < 0) { RefuseBusy(*control, "could not create session child"); continue; }
active = child;
control->CloseLocalCopy();
}
::close(listener);
if (endpoint[0] != '@' && endpoint.compare(0, 6, "tcp://") != 0) ::unlink(endpoint.c_str());
return 0;
}