Files
ladybird/Libraries/LibWebView/MachPortServer.cpp
Aliaksandr Kalenik 4ea4d63008 Everywhere: Replace Unix socket IPC transport with Mach ports on macOS
On macOS, use Mach port messaging instead of Unix domain sockets for
all IPC transport. This makes the transport capable of carrying Mach
port rights as message attachments, which is a prerequisite for sending
IOSurface handles over the main IPC channel (currently sent via a
separate out-of-band path). It also avoids the need for the FD
acknowledgement protocol that TransportSocket requires, since Mach port
right transfers are atomic in the kernel.

Three connection establishment patterns:

- Spawned helper processes (WebContent, RequestServer, etc.) use the
  existing MachPortServer: the child sends its task port with a reply
  port, and the parent responds with a pre-created port pair.

- Socket-bootstrapped connections (WebDriver, BrowserProcess) exchange
  Mach port names over the socket, then drop the socket.

- Pre-created pairs for IPC tests and in-message transport transfer.

Attachment on macOS now wraps a MachPort instead of a file descriptor,
converting between the two via fileport_makeport()/fileport_makefd().

The LibIPC socket transport tests are disabled on macOS since they are
socket-specific.
2026-03-23 18:50:48 +01:00

109 lines
4.7 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibCore/Platform/MachMessageTypes.h>
#include <LibThreading/Thread.h>
#include <LibWebView/MachPortServer.h>
namespace WebView {
MachPortServer::MachPortServer()
: m_thread(Threading::Thread::construct("MachPortServer"sv, [this]() -> intptr_t { thread_loop(); return 0; }))
, m_server_port_name(ByteString::formatted("org.ladybird.Ladybird.helper.{}", getpid()))
{
if (auto err = allocate_server_port(); err.is_error())
dbgln("Failed to allocate server port: {}", err.error());
else
start();
}
MachPortServer::~MachPortServer()
{
stop();
}
void MachPortServer::start()
{
m_thread->start();
}
void MachPortServer::stop()
{
// FIXME: We should join instead (after storing should_stop = false) when we have a way to interrupt the thread's mach_msg call
m_thread->detach();
m_should_stop.store(true, MemoryOrder::memory_order_release);
}
bool MachPortServer::is_initialized()
{
return MACH_PORT_VALID(m_server_port_recv_right.port()) && MACH_PORT_VALID(m_server_port_send_right.port());
}
ErrorOr<void> MachPortServer::allocate_server_port()
{
m_server_port_recv_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
m_server_port_send_right = TRY(m_server_port_recv_right.insert_right(Core::MachPort::MessageRight::MakeSend));
TRY(m_server_port_recv_right.register_with_bootstrap_server(m_server_port_name));
dbgln_if(MACH_PORT_DEBUG, "Success! we created and attached mach port {:x} to bootstrap server with name {}", m_server_port_recv_right.port(), m_server_port_name);
return {};
}
void MachPortServer::thread_loop()
{
while (!m_should_stop.load(MemoryOrder::memory_order_acquire)) {
Core::Platform::ReceivedMachMessage message {};
// Get the pid of the child from the audit trailer so we can associate the port w/it
mach_msg_options_t const options = MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
// FIXME: How can we interrupt this call during application shutdown?
auto const ret = mach_msg(&message.header, options, 0, sizeof(message), m_server_port_recv_right.port(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret != KERN_SUCCESS) {
dbgln("mach_msg failed: {}", mach_error_string(ret));
break;
}
if (message.header.msgh_id == Core::Platform::BACKING_STORE_IOSURFACES_MESSAGE_ID) {
auto pid = static_cast<pid_t>(message.body.parent_iosurface.trailer.msgh_audit.val[5]);
auto const& backing_stores_message = message.body.parent_iosurface;
auto front_child_port = Core::MachPort::adopt_right(backing_stores_message.front_descriptor.name, Core::MachPort::PortRight::Send);
auto back_child_port = Core::MachPort::adopt_right(backing_stores_message.back_descriptor.name, Core::MachPort::PortRight::Send);
auto const& metadata = backing_stores_message.metadata;
if (on_receive_backing_stores)
on_receive_backing_stores({ .pid = pid,
.page_id = metadata.page_id,
.front_backing_store_id = metadata.front_backing_store_id,
.back_backing_store_id = metadata.back_backing_store_id,
.front_backing_store_port = move(front_child_port),
.back_backing_store_port = move(back_child_port) });
continue;
}
if (message.header.msgh_id == Core::Platform::SELF_TASK_PORT_MESSAGE_ID) {
auto const& task_port_message = message.body.parent;
VERIFY(MACH_MSGH_BITS_LOCAL(message.header.msgh_bits) == MACH_MSG_TYPE_MOVE_SEND);
VERIFY(task_port_message.body.msgh_descriptor_count == 1);
VERIFY(task_port_message.port_descriptor.type == MACH_MSG_PORT_DESCRIPTOR);
auto pid = static_cast<pid_t>(task_port_message.trailer.msgh_audit.val[5]);
auto child_port = Core::MachPort::adopt_right(task_port_message.port_descriptor.name, Core::MachPort::PortRight::Send);
// Extract reply port from the message header (kernel swaps local/remote on receive)
auto reply_port = Core::MachPort::adopt_right(message.header.msgh_remote_port, Core::MachPort::PortRight::SendOnce);
dbgln_if(MACH_PORT_DEBUG, "Received child port {:x} from pid {} (reply port {:x})", child_port.port(), pid, reply_port.port());
if (on_receive_child_mach_port)
on_receive_child_mach_port({ pid, move(child_port), move(reply_port) });
continue;
}
VERIFY_NOT_REACHED();
}
}
}