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.
This commit is contained in:
parent
8836f28267
commit
4ea4d63008
37 changed files with 1139 additions and 149 deletions
|
|
@ -85,6 +85,6 @@ private:
|
|||
mach_port_t m_port { MACH_PORT_NULL };
|
||||
};
|
||||
|
||||
Error mach_error_to_error(kern_return_t error);
|
||||
CORE_API Error mach_error_to_error(kern_return_t error);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,23 @@ struct ReceivedMachMessage {
|
|||
} body;
|
||||
};
|
||||
|
||||
struct MessageWithIPCChannelPorts {
|
||||
mach_msg_header_t header;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t receive_port;
|
||||
mach_msg_port_descriptor_t send_port;
|
||||
};
|
||||
|
||||
struct ReceivedIPCChannelPortsMessage {
|
||||
mach_msg_header_t header;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t receive_port;
|
||||
mach_msg_port_descriptor_t send_port;
|
||||
mach_msg_trailer_t trailer;
|
||||
};
|
||||
|
||||
static constexpr mach_msg_id_t SELF_TASK_PORT_MESSAGE_ID = 0x1234CAFE;
|
||||
static constexpr mach_msg_id_t BACKING_STORE_IOSURFACES_MESSAGE_ID = 0x1234CAFF;
|
||||
static constexpr mach_msg_id_t IPC_CHANNEL_PORTS_MESSAGE_ID = 0x4950C002;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@
|
|||
# error "This file is only available on Mach platforms"
|
||||
#endif
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Platform/MachMessageTypes.h>
|
||||
#include <LibCore/Platform/ProcessStatisticsMach.h>
|
||||
|
||||
namespace Core::Platform {
|
||||
|
|
@ -76,36 +73,4 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
|
|||
return {};
|
||||
}
|
||||
|
||||
MachPort register_with_mach_server(ByteString const& server_name)
|
||||
{
|
||||
auto server_port_or_error = Core::MachPort::look_up_from_bootstrap_server(server_name);
|
||||
if (server_port_or_error.is_error()) {
|
||||
dbgln("Failed to lookup server port: {}", server_port_or_error.error());
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto server_port = server_port_or_error.release_value();
|
||||
|
||||
// Send our own task port to the server so they can query statistics about us
|
||||
MessageWithSelfTaskPort message {};
|
||||
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO) | MACH_MSGH_BITS_COMPLEX;
|
||||
message.header.msgh_size = sizeof(message);
|
||||
message.header.msgh_remote_port = server_port.port();
|
||||
message.header.msgh_local_port = MACH_PORT_NULL;
|
||||
message.header.msgh_id = SELF_TASK_PORT_MESSAGE_ID;
|
||||
message.body.msgh_descriptor_count = 1;
|
||||
message.port_descriptor.name = mach_task_self();
|
||||
message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
mach_msg_timeout_t const timeout = 100; // milliseconds
|
||||
|
||||
auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
|
||||
if (send_result != KERN_SUCCESS) {
|
||||
dbgln("Failed to send message to server: {}", mach_error_string(send_result));
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
return server_port;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,7 @@
|
|||
|
||||
#include <LibCore/Export.h>
|
||||
#include <LibCore/Platform/ProcessStatistics.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
namespace Core::Platform {
|
||||
|
||||
CORE_API MachPort register_with_mach_server(ByteString const& server_name);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,6 @@ Attachment Attachment::from_fd(int fd)
|
|||
return attachment;
|
||||
}
|
||||
|
||||
ErrorOr<Attachment> Attachment::clone() const
|
||||
{
|
||||
VERIFY(m_fd != -1);
|
||||
return from_fd(TRY(Core::System::dup(m_fd)));
|
||||
}
|
||||
|
||||
int Attachment::to_fd()
|
||||
{
|
||||
return exchange(m_fd, -1);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibCore/MachPort.h>
|
||||
#endif
|
||||
|
||||
namespace IPC {
|
||||
|
||||
|
|
@ -21,11 +26,22 @@ public:
|
|||
~Attachment();
|
||||
|
||||
static Attachment from_fd(int fd);
|
||||
ErrorOr<Attachment> clone() const;
|
||||
int to_fd();
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
static Attachment from_mach_port(Core::MachPort, Core::MachPort::MessageRight);
|
||||
Core::MachPort const& mach_port() const { return m_port; }
|
||||
Core::MachPort::MessageRight message_right() const { return m_message_right; }
|
||||
Core::MachPort release_mach_port();
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AK_OS_MACOS)
|
||||
Core::MachPort m_port;
|
||||
Core::MachPort::MessageRight m_message_right { Core::MachPort::MessageRight::MoveSend };
|
||||
#else
|
||||
int m_fd { -1 };
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
74
Libraries/LibIPC/AttachmentMachPort.cpp
Normal file
74
Libraries/LibIPC/AttachmentMachPort.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Format.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/Attachment.h>
|
||||
|
||||
// fileport_makeport() and fileport_makefd() are private macOS APIs that convert
|
||||
// between file descriptors and Mach port rights. Since Mach messages can only
|
||||
// carry port rights (not file descriptors), we convert FDs to "file ports" for
|
||||
// transmission and convert them back on the receiving side. These APIs are stable
|
||||
// and used by Apple's own frameworks (including XPC).
|
||||
extern "C" {
|
||||
int fileport_makeport(int fd, mach_port_t* port);
|
||||
int fileport_makefd(mach_port_t port);
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
||||
Attachment::Attachment(Attachment&& other)
|
||||
: m_port(move(other.m_port))
|
||||
, m_message_right(other.m_message_right)
|
||||
{
|
||||
}
|
||||
|
||||
Attachment& Attachment::operator=(Attachment&& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
m_port = move(other.m_port);
|
||||
m_message_right = other.m_message_right;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Attachment::~Attachment() = default;
|
||||
|
||||
Attachment Attachment::from_fd(int fd)
|
||||
{
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
auto result = fileport_makeport(fd, &port);
|
||||
(void)Core::System::close(fd);
|
||||
VERIFY(result == 0);
|
||||
return from_mach_port(Core::MachPort::adopt_right(port, Core::MachPort::PortRight::Send), Core::MachPort::MessageRight::MoveSend);
|
||||
}
|
||||
|
||||
int Attachment::to_fd()
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(m_port.port()));
|
||||
int fd = fileport_makefd(m_port.port());
|
||||
VERIFY(fd >= 0);
|
||||
mach_port_deallocate(mach_task_self(), m_port.release());
|
||||
return fd;
|
||||
}
|
||||
|
||||
Attachment Attachment::from_mach_port(Core::MachPort port, Core::MachPort::MessageRight right)
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(port.port()));
|
||||
Attachment attachment;
|
||||
attachment.m_port = move(port);
|
||||
attachment.m_message_right = right;
|
||||
return attachment;
|
||||
}
|
||||
|
||||
Core::MachPort Attachment::release_mach_port()
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(m_port.port()));
|
||||
return move(m_port);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,7 +6,14 @@ set(SOURCES
|
|||
TransportHandle.cpp
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
if (APPLE AND NOT IOS)
|
||||
list(APPEND SOURCES
|
||||
AttachmentMachPort.cpp
|
||||
File.cpp
|
||||
Message.cpp
|
||||
TransportBootstrapMach.cpp
|
||||
TransportMachPort.cpp)
|
||||
elseif (UNIX)
|
||||
list(APPEND SOURCES
|
||||
Attachment.cpp
|
||||
File.cpp
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Platform.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
|
|
@ -20,6 +21,17 @@ class File;
|
|||
class Stub;
|
||||
class TransportHandle;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
class TransportMachPort;
|
||||
using Transport = TransportMachPort;
|
||||
#elif !defined(AK_OS_WINDOWS)
|
||||
class TransportSocket;
|
||||
using Transport = TransportSocket;
|
||||
#else
|
||||
class TransportSocketWindows;
|
||||
using Transport = TransportSocketWindows;
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<void> encode(Encoder&, T const&);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,16 +6,30 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibCore/SystemServerTakeover.h>
|
||||
#include <AK/Platform.h>
|
||||
#include <LibIPC/ConnectionFromClient.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
#else
|
||||
# include <LibCore/SystemServerTakeover.h>
|
||||
#endif
|
||||
|
||||
namespace IPC {
|
||||
|
||||
template<typename ConnectionFromClientType, typename... Args>
|
||||
ErrorOr<NonnullRefPtr<ConnectionFromClientType>> take_over_accepted_client_from_system_server(Args&&... args)
|
||||
ErrorOr<NonnullRefPtr<ConnectionFromClientType>> take_over_accepted_client_from_system_server([[maybe_unused]] StringView mach_server_name, Args&&... args)
|
||||
{
|
||||
#if defined(AK_OS_MACOS)
|
||||
auto ports = TRY(bootstrap_transport_from_mach_server(mach_server_name));
|
||||
return IPC::new_client_connection<ConnectionFromClientType>(
|
||||
make<IPC::Transport>(move(ports.receive_right), move(ports.send_right)),
|
||||
forward<Args>(args)...);
|
||||
#else
|
||||
auto socket = TRY(Core::take_over_socket_from_system_server());
|
||||
return IPC::new_client_connection<ConnectionFromClientType>(make<IPC::Transport>(move(socket)), forward<Args>(args)...);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Platform.h>
|
||||
#include <LibCore/Socket.h>
|
||||
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportMachPort.h>
|
||||
#elif !defined(AK_OS_WINDOWS)
|
||||
# include <LibIPC/TransportSocket.h>
|
||||
#else
|
||||
# include <LibIPC/TransportSocketWindows.h>
|
||||
|
|
@ -16,11 +19,4 @@
|
|||
|
||||
namespace IPC {
|
||||
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
// Unix Domain Sockets
|
||||
using Transport = TransportSocket;
|
||||
#else
|
||||
using Transport = TransportSocketWindows;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
184
Libraries/LibIPC/TransportBootstrapMach.cpp
Normal file
184
Libraries/LibIPC/TransportBootstrapMach.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Platform/MachMessageTypes.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/TransportBootstrapMach.h>
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
static ErrorOr<void> write_exact(int fd, ReadonlyBytes bytes)
|
||||
{
|
||||
size_t total_written = 0;
|
||||
while (total_written < bytes.size()) {
|
||||
auto nwritten = TRY(Core::System::write(fd, bytes.slice(total_written)));
|
||||
VERIFY(nwritten > 0);
|
||||
total_written += nwritten;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> read_exact(int fd, Bytes bytes)
|
||||
{
|
||||
size_t total_read = 0;
|
||||
while (total_read < bytes.size()) {
|
||||
auto nread = TRY(Core::System::read(fd, bytes.slice(total_read)));
|
||||
VERIFY(nread > 0);
|
||||
total_read += nread;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_server_port(Core::MachPort const& server_port)
|
||||
{
|
||||
auto reply_port = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
|
||||
Core::Platform::MessageWithSelfTaskPort message {};
|
||||
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX;
|
||||
message.header.msgh_size = sizeof(message);
|
||||
message.header.msgh_remote_port = server_port.port();
|
||||
message.header.msgh_local_port = reply_port.port();
|
||||
message.header.msgh_id = Core::Platform::SELF_TASK_PORT_MESSAGE_ID;
|
||||
message.body.msgh_descriptor_count = 1;
|
||||
message.port_descriptor.name = mach_task_self();
|
||||
message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
mach_msg_timeout_t const send_timeout = 100;
|
||||
auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, send_timeout, MACH_PORT_NULL);
|
||||
if (send_result != KERN_SUCCESS)
|
||||
return Core::mach_error_to_error(send_result);
|
||||
|
||||
Core::Platform::ReceivedIPCChannelPortsMessage reply {};
|
||||
mach_msg_timeout_t const reply_timeout = 5000;
|
||||
auto const recv_result = mach_msg(&reply.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(reply),
|
||||
reply_port.port(), reply_timeout, MACH_PORT_NULL);
|
||||
if (recv_result != KERN_SUCCESS)
|
||||
return Core::mach_error_to_error(recv_result);
|
||||
|
||||
VERIFY(reply.header.msgh_id == Core::Platform::IPC_CHANNEL_PORTS_MESSAGE_ID);
|
||||
VERIFY(reply.body.msgh_descriptor_count == 2);
|
||||
VERIFY(reply.receive_port.type == MACH_MSG_PORT_DESCRIPTOR);
|
||||
VERIFY(reply.receive_port.disposition == MACH_MSG_TYPE_MOVE_RECEIVE);
|
||||
VERIFY(reply.send_port.type == MACH_MSG_PORT_DESCRIPTOR);
|
||||
VERIFY(reply.send_port.disposition == MACH_MSG_TYPE_MOVE_SEND);
|
||||
|
||||
return TransportBootstrapMachPorts {
|
||||
.receive_right = Core::MachPort::adopt_right(reply.receive_port.name, Core::MachPort::PortRight::Receive),
|
||||
.send_right = Core::MachPort::adopt_right(reply.send_port.name, Core::MachPort::PortRight::Send),
|
||||
};
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_mach_server(StringView server_name)
|
||||
{
|
||||
auto server_port = TRY(Core::MachPort::look_up_from_bootstrap_server(ByteString { server_name }));
|
||||
return bootstrap_transport_from_server_port(server_port);
|
||||
}
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_over_socket(Core::LocalSocket& socket)
|
||||
{
|
||||
auto our_receive_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto our_name = ByteString::formatted("org.ladybird.ipc.{}.{}", Core::System::getpid(), our_receive_right.port());
|
||||
TRY(our_receive_right.register_with_bootstrap_server(our_name));
|
||||
|
||||
auto socket_fd = socket.fd().value();
|
||||
TRY(socket.set_blocking(true));
|
||||
|
||||
auto const name_bytes = our_name.bytes();
|
||||
u32 const name_length = static_cast<u32>(name_bytes.size());
|
||||
TRY(write_exact(socket_fd, ReadonlyBytes { reinterpret_cast<u8 const*>(&name_length), sizeof(name_length) }));
|
||||
TRY(write_exact(socket_fd, name_bytes));
|
||||
|
||||
u32 peer_name_length = 0;
|
||||
TRY(read_exact(socket_fd, Bytes { reinterpret_cast<u8*>(&peer_name_length), sizeof(peer_name_length) }));
|
||||
VERIFY(peer_name_length > 0);
|
||||
VERIFY(peer_name_length <= 256);
|
||||
|
||||
auto peer_name_buffer = TRY(ByteBuffer::create_uninitialized(peer_name_length));
|
||||
TRY(read_exact(socket_fd, peer_name_buffer.bytes()));
|
||||
|
||||
auto peer_name = ByteString { peer_name_buffer.bytes() };
|
||||
auto peer_send_right = TRY(Core::MachPort::look_up_from_bootstrap_server(peer_name));
|
||||
|
||||
u8 ack = 1;
|
||||
TRY(write_exact(socket_fd, { &ack, 1 }));
|
||||
TRY(read_exact(socket_fd, { &ack, 1 }));
|
||||
VERIFY(ack == 1);
|
||||
|
||||
return TransportBootstrapMachPorts {
|
||||
.receive_right = move(our_receive_right),
|
||||
.send_right = move(peer_send_right),
|
||||
};
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports)
|
||||
{
|
||||
Core::Platform::MessageWithIPCChannelPorts message {};
|
||||
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | MACH_MSGH_BITS_COMPLEX;
|
||||
message.header.msgh_size = sizeof(message);
|
||||
message.header.msgh_remote_port = reply_port.release();
|
||||
message.header.msgh_local_port = MACH_PORT_NULL;
|
||||
message.header.msgh_id = Core::Platform::IPC_CHANNEL_PORTS_MESSAGE_ID;
|
||||
message.body.msgh_descriptor_count = 2;
|
||||
message.receive_port.name = ports.receive_right.release();
|
||||
message.receive_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
|
||||
message.receive_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
message.send_port.name = ports.send_right.release();
|
||||
message.send_port.disposition = MACH_MSG_TYPE_MOVE_SEND;
|
||||
message.send_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
|
||||
mach_msg_timeout_t const timeout = 5000;
|
||||
auto const ret = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(message), 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
|
||||
VERIFY(ret == KERN_SUCCESS);
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::register_pending_transport(pid_t pid, TransportBootstrapMachPorts ports)
|
||||
{
|
||||
m_pending_bootstrap_mutex.lock();
|
||||
auto pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
m_pending_bootstrap.set(pid, WaitingForPorts { move(ports) });
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
pending.release_value().visit(
|
||||
[&](WaitingForPorts&) {
|
||||
VERIFY_NOT_REACHED();
|
||||
},
|
||||
[&](WaitingForReplyPort& waiting) {
|
||||
send_transport_ports_to_child(move(waiting.reply_port), move(ports));
|
||||
});
|
||||
}
|
||||
|
||||
void TransportBootstrapMachServer::register_reply_port(pid_t pid, Core::MachPort reply_port)
|
||||
{
|
||||
m_pending_bootstrap_mutex.lock();
|
||||
auto pending = m_pending_bootstrap.take(pid);
|
||||
if (!pending.has_value()) {
|
||||
m_pending_bootstrap.set(pid, WaitingForReplyPort { move(reply_port) });
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
m_pending_bootstrap_mutex.unlock();
|
||||
pending.release_value().visit(
|
||||
[&](WaitingForPorts& waiting) {
|
||||
send_transport_ports_to_child(move(reply_port), move(waiting.ports));
|
||||
},
|
||||
[&](WaitingForReplyPort&) {
|
||||
VERIFY_NOT_REACHED();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
57
Libraries/LibIPC/TransportBootstrapMach.h
Normal file
57
Libraries/LibIPC/TransportBootstrapMach.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Platform.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Variant.h>
|
||||
|
||||
#if !defined(AK_OS_MACH)
|
||||
# error "TransportBootstrapMach is only available on Mach platforms"
|
||||
#endif
|
||||
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibThreading/Mutex.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
struct TransportBootstrapMachPorts {
|
||||
Core::MachPort receive_right;
|
||||
Core::MachPort send_right;
|
||||
};
|
||||
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_mach_server(StringView server_name);
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_from_server_port(Core::MachPort const& server_port);
|
||||
ErrorOr<TransportBootstrapMachPorts> bootstrap_transport_over_socket(Core::LocalSocket&);
|
||||
|
||||
class TransportBootstrapMachServer {
|
||||
AK_MAKE_NONCOPYABLE(TransportBootstrapMachServer);
|
||||
|
||||
public:
|
||||
TransportBootstrapMachServer() = default;
|
||||
|
||||
void register_pending_transport(pid_t, TransportBootstrapMachPorts);
|
||||
void register_reply_port(pid_t, Core::MachPort reply_port);
|
||||
|
||||
private:
|
||||
struct WaitingForPorts {
|
||||
TransportBootstrapMachPorts ports;
|
||||
};
|
||||
struct WaitingForReplyPort {
|
||||
Core::MachPort reply_port;
|
||||
};
|
||||
using PendingBootstrapState = Variant<WaitingForPorts, WaitingForReplyPort>;
|
||||
|
||||
static void send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports);
|
||||
|
||||
Threading::Mutex m_pending_bootstrap_mutex;
|
||||
HashMap<pid_t, PendingBootstrapState> m_pending_bootstrap;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -5,14 +5,57 @@
|
|||
*/
|
||||
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibIPC/Attachment.h>
|
||||
#include <LibIPC/Decoder.h>
|
||||
#include <LibIPC/Encoder.h>
|
||||
#include <LibIPC/File.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
|
||||
TransportHandle::TransportHandle(Core::MachPort receive_right, Core::MachPort send_right)
|
||||
: m_receive_right(move(receive_right))
|
||||
, m_send_right(move(send_right))
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(m_receive_right.port()));
|
||||
VERIFY(MACH_PORT_VALID(m_send_right.port()));
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<Transport>> TransportHandle::create_transport() const
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(m_receive_right.port()));
|
||||
VERIFY(MACH_PORT_VALID(m_send_right.port()));
|
||||
return make<Transport>(move(m_receive_right), move(m_send_right));
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, TransportHandle const& handle)
|
||||
{
|
||||
VERIFY(MACH_PORT_VALID(handle.m_receive_right.port()));
|
||||
VERIFY(MACH_PORT_VALID(handle.m_send_right.port()));
|
||||
TRY(encoder.append_attachment(Attachment::from_mach_port(move(handle.m_receive_right), Core::MachPort::MessageRight::MoveReceive)));
|
||||
TRY(encoder.append_attachment(Attachment::from_mach_port(move(handle.m_send_right), Core::MachPort::MessageRight::MoveSend)));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<TransportHandle> decode(Decoder& decoder)
|
||||
{
|
||||
auto& attachments = decoder.attachments();
|
||||
VERIFY(attachments.size() >= 2);
|
||||
auto recv_attachment = attachments.dequeue();
|
||||
auto send_attachment = attachments.dequeue();
|
||||
VERIFY(recv_attachment.message_right() == Core::MachPort::MessageRight::MoveReceive);
|
||||
VERIFY(send_attachment.message_right() == Core::MachPort::MessageRight::MoveSend);
|
||||
auto receive_right = recv_attachment.release_mach_port();
|
||||
auto send_right = send_attachment.release_mach_port();
|
||||
return TransportHandle { move(receive_right), move(send_right) };
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
TransportHandle::TransportHandle(File file)
|
||||
: m_file(move(file))
|
||||
{
|
||||
|
|
@ -38,4 +81,6 @@ ErrorOr<TransportHandle> decode(Decoder& decoder)
|
|||
return TransportHandle { move(file) };
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,16 +13,12 @@
|
|||
#include <LibIPC/File.h>
|
||||
#include <LibIPC/Forward.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
class TransportSocket;
|
||||
using Transport = TransportSocket;
|
||||
#else
|
||||
class TransportSocketWindows;
|
||||
using Transport = TransportSocketWindows;
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibCore/MachPort.h>
|
||||
#endif
|
||||
|
||||
namespace IPC {
|
||||
|
||||
class TransportHandle {
|
||||
AK_MAKE_NONCOPYABLE(TransportHandle);
|
||||
|
||||
|
|
@ -31,18 +27,27 @@ public:
|
|||
TransportHandle(TransportHandle&&) = default;
|
||||
TransportHandle& operator=(TransportHandle&&) = default;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
TransportHandle(Core::MachPort receive_right, Core::MachPort send_right);
|
||||
#else
|
||||
explicit TransportHandle(File);
|
||||
#endif
|
||||
|
||||
ErrorOr<NonnullOwnPtr<Transport>> create_transport() const;
|
||||
|
||||
explicit TransportHandle(File);
|
||||
|
||||
private:
|
||||
#if defined(AK_OS_MACOS)
|
||||
mutable Core::MachPort m_receive_right;
|
||||
mutable Core::MachPort m_send_right;
|
||||
#else
|
||||
mutable File m_file;
|
||||
#endif
|
||||
|
||||
template<typename U>
|
||||
friend ErrorOr<void> encode(Encoder&, U const&);
|
||||
|
||||
template<typename U>
|
||||
friend ErrorOr<U> decode(Decoder&);
|
||||
|
||||
mutable File m_file;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
416
Libraries/LibIPC/TransportMachPort.cpp
Normal file
416
Libraries/LibIPC/TransportMachPort.cpp
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/TransportBootstrapMach.h>
|
||||
#include <LibIPC/TransportMachPort.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
|
||||
#include <mach/mach.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
static void set_mach_port_queue_limit(mach_port_t port)
|
||||
{
|
||||
mach_port_limits_t limits { .mpl_qlimit = MACH_PORT_QLIMIT_MAX };
|
||||
// Mach receive rights start with a small queue. Raise it so short bursts can wait in the kernel if this
|
||||
// transport thread falls behind for a moment.
|
||||
mach_port_set_attributes(mach_task_self(), port, MACH_PORT_LIMITS_INFO,
|
||||
reinterpret_cast<mach_port_info_t>(&limits), MACH_PORT_LIMITS_INFO_COUNT);
|
||||
}
|
||||
|
||||
static Attachment attachment_from_descriptor(mach_msg_port_descriptor_t const& descriptor)
|
||||
{
|
||||
VERIFY(descriptor.type == MACH_MSG_PORT_DESCRIPTOR);
|
||||
|
||||
switch (descriptor.disposition) {
|
||||
case MACH_MSG_TYPE_MOVE_SEND:
|
||||
return Attachment::from_mach_port(
|
||||
Core::MachPort::adopt_right(descriptor.name, Core::MachPort::PortRight::Send),
|
||||
Core::MachPort::MessageRight::MoveSend);
|
||||
case MACH_MSG_TYPE_MOVE_RECEIVE:
|
||||
return Attachment::from_mach_port(
|
||||
Core::MachPort::adopt_right(descriptor.name, Core::MachPort::PortRight::Receive),
|
||||
Core::MachPort::MessageRight::MoveReceive);
|
||||
case MACH_MSG_TYPE_MOVE_SEND_ONCE:
|
||||
return Attachment::from_mach_port(
|
||||
Core::MachPort::adopt_right(descriptor.name, Core::MachPort::PortRight::SendOnce),
|
||||
Core::MachPort::MessageRight::MoveSendOnce);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TransportMachPort>> TransportMachPort::from_socket(NonnullOwnPtr<Core::LocalSocket> socket)
|
||||
{
|
||||
auto ports = TRY(bootstrap_transport_over_socket(*socket));
|
||||
return make<TransportMachPort>(move(ports.receive_right), move(ports.send_right));
|
||||
}
|
||||
|
||||
ErrorOr<TransportMachPort::Paired> TransportMachPort::create_paired()
|
||||
{
|
||||
auto port_a_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto port_a_send = TRY(port_a_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
|
||||
auto port_b_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto port_b_send = TRY(port_b_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
|
||||
return Paired {
|
||||
make<TransportMachPort>(move(port_a_recv), move(port_b_send)),
|
||||
TransportHandle { move(port_b_recv), move(port_a_send) },
|
||||
};
|
||||
}
|
||||
|
||||
TransportMachPort::TransportMachPort(Core::MachPort receive_right, Core::MachPort send_right)
|
||||
: m_receive_port(move(receive_right))
|
||||
, m_send_port(move(send_right))
|
||||
{
|
||||
m_port_set = MUST(Core::MachPort::create_with_right(Core::MachPort::PortRight::PortSet));
|
||||
// This thread waits on a port set, not one port. Add the main receive port so the same wait can handle
|
||||
// messages from the peer.
|
||||
auto ret = mach_port_insert_member(mach_task_self(), m_receive_port.port(), m_port_set.port());
|
||||
VERIFY(ret == KERN_SUCCESS);
|
||||
|
||||
m_wakeup_receive_port = MUST(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
m_wakeup_send_port = MUST(m_wakeup_receive_port.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
// Add the private wakeup port to the same set so queued sends can wake the blocking mach_msg() call.
|
||||
ret = mach_port_insert_member(mach_task_self(), m_wakeup_receive_port.port(), m_port_set.port());
|
||||
VERIFY(ret == KERN_SUCCESS);
|
||||
|
||||
auto fds = MUST(Core::System::pipe2(O_CLOEXEC | O_NONBLOCK));
|
||||
m_notify_hook_read_fd = adopt_ref(*new AutoCloseFileDescriptor(fds[0]));
|
||||
m_notify_hook_write_fd = adopt_ref(*new AutoCloseFileDescriptor(fds[1]));
|
||||
|
||||
set_mach_port_queue_limit(m_receive_port.port());
|
||||
|
||||
mach_port_t prev = MACH_PORT_NULL;
|
||||
// Ask the kernel to send MACH_NOTIFY_NO_SENDERS to our receive port when the peer loses its last send
|
||||
// right. This is how the transport notices that the peer went away.
|
||||
mach_port_request_notification(mach_task_self(),
|
||||
m_receive_port.port(),
|
||||
MACH_NOTIFY_NO_SENDERS,
|
||||
0,
|
||||
m_receive_port.port(),
|
||||
MACH_MSG_TYPE_MAKE_SEND_ONCE,
|
||||
&prev);
|
||||
if (MACH_PORT_VALID(prev)) {
|
||||
// If an older notification right was replaced, mach_port_request_notification returns it in `prev`.
|
||||
// Drop it here so we do not leak that stale send-once right.
|
||||
mach_port_deallocate(mach_task_self(), prev);
|
||||
}
|
||||
|
||||
m_io_thread = Threading::Thread::construct("IPC IO (Mach)"sv, [this] { return io_thread_loop(); });
|
||||
m_io_thread->start();
|
||||
}
|
||||
|
||||
TransportMachPort::~TransportMachPort()
|
||||
{
|
||||
stop_io_thread(IOThreadState::Stopped);
|
||||
m_read_hook_notifier.clear();
|
||||
}
|
||||
|
||||
void TransportMachPort::stop_io_thread(IOThreadState desired_state)
|
||||
{
|
||||
m_io_thread_state.store(desired_state, AK::MemoryOrder::memory_order_release);
|
||||
wake_io_thread();
|
||||
if (m_io_thread && m_io_thread->needs_to_be_joined())
|
||||
(void)m_io_thread->join();
|
||||
}
|
||||
|
||||
void TransportMachPort::wake_io_thread()
|
||||
{
|
||||
if (!MACH_PORT_VALID(m_wakeup_send_port.port()))
|
||||
return;
|
||||
|
||||
mach_msg_header_t header {};
|
||||
header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
||||
header.msgh_size = sizeof(header);
|
||||
header.msgh_remote_port = m_wakeup_send_port.port();
|
||||
header.msgh_local_port = MACH_PORT_NULL;
|
||||
header.msgh_id = IPC_WAKEUP_MESSAGE_ID;
|
||||
|
||||
// Send a header-only wakeup message to the private wakeup port. Because that port is in the same port set,
|
||||
// it breaks the blocking mach_msg() call so the thread can flush queued sends.
|
||||
mach_msg(&header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(header), 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
|
||||
}
|
||||
|
||||
void TransportMachPort::notify_read_available()
|
||||
{
|
||||
if (!m_notify_hook_write_fd)
|
||||
return;
|
||||
|
||||
Array<u8, 1> bytes = { 0 };
|
||||
(void)Core::System::write(m_notify_hook_write_fd->value(), bytes);
|
||||
}
|
||||
|
||||
void TransportMachPort::mark_peer_eof()
|
||||
{
|
||||
{
|
||||
Threading::MutexLocker locker(m_incoming_mutex);
|
||||
m_peer_eof = true;
|
||||
}
|
||||
m_incoming_cv.broadcast();
|
||||
notify_read_available();
|
||||
}
|
||||
|
||||
intptr_t TransportMachPort::io_thread_loop()
|
||||
{
|
||||
static constexpr size_t RECV_BUFFER_SIZE = 65536;
|
||||
auto buffer = Vector<u8>();
|
||||
buffer.resize(RECV_BUFFER_SIZE);
|
||||
|
||||
for (;;) {
|
||||
if (m_io_thread_state.load() == IOThreadState::Stopped)
|
||||
break;
|
||||
|
||||
Vector<PendingMessage> messages_to_send;
|
||||
{
|
||||
Threading::MutexLocker locker(m_send_mutex);
|
||||
messages_to_send = move(m_pending_send_messages);
|
||||
}
|
||||
for (auto& message : messages_to_send)
|
||||
send_mach_message(message);
|
||||
|
||||
if (m_io_thread_state.load() == IOThreadState::SendPendingMessagesAndStop) {
|
||||
Threading::MutexLocker locker(m_send_mutex);
|
||||
if (!m_pending_send_messages.is_empty())
|
||||
continue;
|
||||
m_io_thread_state = IOThreadState::Stopped;
|
||||
break;
|
||||
}
|
||||
|
||||
auto* header = reinterpret_cast<mach_msg_header_t*>(buffer.data());
|
||||
// Wait on the whole port set so one loop can handle peer messages, internal wakeups, and kernel
|
||||
// notifications like MACH_NOTIFY_NO_SENDERS.
|
||||
auto const ret = mach_msg(header, MACH_RCV_MSG | MACH_RCV_LARGE, 0, buffer.size(),
|
||||
m_port_set.port(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
|
||||
if (ret == MACH_RCV_TOO_LARGE) {
|
||||
auto needed_size = header->msgh_size + sizeof(mach_msg_trailer_t);
|
||||
buffer.resize(needed_size);
|
||||
header = reinterpret_cast<mach_msg_header_t*>(buffer.data());
|
||||
// MACH_RCV_LARGE tells us the needed size without removing the message. Resize the buffer and try
|
||||
// again so large messages, including large attachment batches, still work.
|
||||
auto const retry_ret = mach_msg(header, MACH_RCV_MSG, 0, needed_size,
|
||||
m_port_set.port(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
if (retry_ret != KERN_SUCCESS) {
|
||||
dbgln("TransportMachPort: mach_msg retry failed: {}", mach_error_string(retry_ret));
|
||||
m_io_thread_state = IOThreadState::Stopped;
|
||||
break;
|
||||
}
|
||||
} else if (ret != KERN_SUCCESS) {
|
||||
dbgln("TransportMachPort: mach_msg receive failed: {}", mach_error_string(ret));
|
||||
m_io_thread_state = IOThreadState::Stopped;
|
||||
break;
|
||||
}
|
||||
|
||||
if (header->msgh_local_port == m_wakeup_receive_port.port()) {
|
||||
VERIFY(header->msgh_id == IPC_WAKEUP_MESSAGE_ID);
|
||||
continue;
|
||||
}
|
||||
|
||||
VERIFY(header->msgh_local_port == m_receive_port.port());
|
||||
|
||||
switch (header->msgh_id) {
|
||||
case MACH_NOTIFY_NO_SENDERS:
|
||||
mark_peer_eof();
|
||||
continue;
|
||||
case MACH_NOTIFY_SEND_ONCE:
|
||||
continue;
|
||||
case IPC_DATA_MESSAGE_ID:
|
||||
process_received_message(buffer.data());
|
||||
continue;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
VERIFY(m_io_thread_state == IOThreadState::Stopped);
|
||||
mark_peer_eof();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TransportMachPort::send_mach_message(PendingMessage& msg)
|
||||
{
|
||||
auto const& bytes = msg.bytes;
|
||||
auto& attachments = msg.attachments;
|
||||
size_t port_count = attachments.size();
|
||||
size_t total_desc_count = port_count + 1; // +1 for OOL payload
|
||||
|
||||
size_t msg_size = sizeof(mach_msg_header_t)
|
||||
+ sizeof(mach_msg_body_t)
|
||||
+ (port_count * sizeof(mach_msg_port_descriptor_t))
|
||||
+ sizeof(mach_msg_ool_descriptor_t);
|
||||
|
||||
if (m_send_buffer.size() < msg_size)
|
||||
m_send_buffer.resize(msg_size);
|
||||
|
||||
auto* header = reinterpret_cast<mach_msg_header_t*>(m_send_buffer.data());
|
||||
header->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
|
||||
header->msgh_size = msg_size;
|
||||
header->msgh_remote_port = m_send_port.port();
|
||||
header->msgh_local_port = MACH_PORT_NULL;
|
||||
header->msgh_id = IPC_DATA_MESSAGE_ID;
|
||||
|
||||
auto* body = reinterpret_cast<mach_msg_body_t*>(header + 1);
|
||||
body->msgh_descriptor_count = total_desc_count;
|
||||
|
||||
auto* desc_ptr = reinterpret_cast<mach_msg_port_descriptor_t*>(body + 1);
|
||||
for (size_t i = 0; i < port_count; ++i) {
|
||||
auto disposition = static_cast<mach_msg_type_name_t>(attachments[i].message_right());
|
||||
auto port = attachments[i].release_mach_port();
|
||||
desc_ptr[i].name = port.release();
|
||||
desc_ptr[i].disposition = disposition;
|
||||
desc_ptr[i].type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
auto* ool_desc = reinterpret_cast<mach_msg_ool_descriptor_t*>(&desc_ptr[port_count]);
|
||||
ool_desc->address = const_cast<void*>(static_cast<void const*>(bytes.data()));
|
||||
ool_desc->size = bytes.size();
|
||||
ool_desc->deallocate = false;
|
||||
ool_desc->copy = MACH_MSG_VIRTUAL_COPY;
|
||||
ool_desc->type = MACH_MSG_OOL_DESCRIPTOR;
|
||||
|
||||
// Send one complex Mach message: port descriptors for attachments and one out-of-line region for the byte
|
||||
// payload. This keeps right transfer atomic and lets the kernel use virtual-copy for the payload.
|
||||
auto const ret = mach_msg(header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, msg_size, 0,
|
||||
MACH_PORT_NULL, 5000 /* 5 sec timeout */, MACH_PORT_NULL);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
dbgln("TransportMachPort: send failed: {} (send_port={:x})", mach_error_string(ret), m_send_port.port());
|
||||
mark_peer_eof();
|
||||
}
|
||||
}
|
||||
|
||||
void TransportMachPort::process_received_message(u8* buffer)
|
||||
{
|
||||
auto* header = reinterpret_cast<mach_msg_header_t*>(buffer);
|
||||
VERIFY(header->msgh_bits & MACH_MSGH_BITS_COMPLEX);
|
||||
auto* body = reinterpret_cast<mach_msg_body_t*>(header + 1);
|
||||
VERIFY(body->msgh_descriptor_count > 0);
|
||||
auto attachment_count = body->msgh_descriptor_count - 1;
|
||||
auto* descriptors = reinterpret_cast<mach_msg_port_descriptor_t*>(body + 1);
|
||||
auto const* payload = reinterpret_cast<mach_msg_ool_descriptor_t const*>(&descriptors[attachment_count]);
|
||||
|
||||
auto message = make<Message>();
|
||||
for (unsigned int i = 0; i < attachment_count; ++i)
|
||||
message->attachments.enqueue(attachment_from_descriptor(descriptors[i]));
|
||||
|
||||
VERIFY(payload->type == MACH_MSG_OOL_DESCRIPTOR);
|
||||
if (payload->size > 0) {
|
||||
message->bytes.append(static_cast<u8 const*>(payload->address), payload->size);
|
||||
// The out-of-line payload arrives as a temporary mapping in this task. After copying it into our queue,
|
||||
// unmap that region so each receive does not leak virtual memory.
|
||||
vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(payload->address), payload->size);
|
||||
}
|
||||
|
||||
if (message->bytes.is_empty() && message->attachments.is_empty())
|
||||
return;
|
||||
|
||||
{
|
||||
Threading::MutexLocker locker(m_incoming_mutex);
|
||||
m_incoming_messages.append(move(message));
|
||||
}
|
||||
m_incoming_cv.signal();
|
||||
notify_read_available();
|
||||
}
|
||||
|
||||
void TransportMachPort::set_up_read_hook(Function<void()> hook)
|
||||
{
|
||||
m_on_read_hook = move(hook);
|
||||
m_read_hook_notifier = Core::Notifier::construct(m_notify_hook_read_fd->value(), Core::NotificationType::Read);
|
||||
m_read_hook_notifier->on_activation = [this] {
|
||||
char buf[64];
|
||||
(void)Core::System::read(m_notify_hook_read_fd->value(), { buf, sizeof(buf) });
|
||||
if (m_on_read_hook)
|
||||
m_on_read_hook();
|
||||
};
|
||||
|
||||
{
|
||||
Threading::MutexLocker locker(m_incoming_mutex);
|
||||
if (!m_incoming_messages.is_empty())
|
||||
notify_read_available();
|
||||
}
|
||||
}
|
||||
|
||||
bool TransportMachPort::is_open() const
|
||||
{
|
||||
return m_is_open && !m_peer_eof;
|
||||
}
|
||||
|
||||
void TransportMachPort::close()
|
||||
{
|
||||
m_is_open = false;
|
||||
stop_io_thread(IOThreadState::Stopped);
|
||||
}
|
||||
|
||||
void TransportMachPort::close_after_sending_all_pending_messages()
|
||||
{
|
||||
stop_io_thread(IOThreadState::SendPendingMessagesAndStop);
|
||||
m_is_open = false;
|
||||
}
|
||||
|
||||
void TransportMachPort::wait_until_readable()
|
||||
{
|
||||
Threading::MutexLocker lock(m_incoming_mutex);
|
||||
while (m_incoming_messages.is_empty() && !m_peer_eof)
|
||||
m_incoming_cv.wait();
|
||||
}
|
||||
|
||||
void TransportMachPort::post_message(Vector<u8> const& bytes, Vector<Attachment>& attachments)
|
||||
{
|
||||
{
|
||||
Threading::MutexLocker locker(m_send_mutex);
|
||||
m_pending_send_messages.append(PendingMessage { bytes, move(attachments) });
|
||||
}
|
||||
wake_io_thread();
|
||||
}
|
||||
|
||||
TransportMachPort::ShouldShutdown TransportMachPort::read_as_many_messages_as_possible_without_blocking(Function<void(Message&&)>&& callback)
|
||||
{
|
||||
Vector<NonnullOwnPtr<Message>> messages;
|
||||
{
|
||||
Threading::MutexLocker locker(m_incoming_mutex);
|
||||
messages = move(m_incoming_messages);
|
||||
}
|
||||
for (auto& message : messages)
|
||||
callback(move(*message));
|
||||
return m_peer_eof ? ShouldShutdown::Yes : ShouldShutdown::No;
|
||||
}
|
||||
|
||||
ErrorOr<TransportHandle> TransportMachPort::release_for_transfer()
|
||||
{
|
||||
stop_io_thread(IOThreadState::Stopped);
|
||||
m_is_open = false;
|
||||
|
||||
mach_port_t prev = MACH_PORT_NULL;
|
||||
// Remove the no-senders registration before giving this receive right to another owner. Otherwise the old
|
||||
// transport could still get disconnect notifications for a port it no longer owns.
|
||||
auto ret = mach_port_request_notification(mach_task_self(),
|
||||
m_receive_port.port(),
|
||||
MACH_NOTIFY_NO_SENDERS,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TYPE_MAKE_SEND_ONCE,
|
||||
&prev);
|
||||
VERIFY(ret == KERN_SUCCESS);
|
||||
if (MACH_PORT_VALID(prev))
|
||||
// Removing the registration still returns the old send-once notification right in `prev`.
|
||||
// Release it so the transferred port does not keep leftover notification state.
|
||||
mach_port_deallocate(mach_task_self(), prev);
|
||||
|
||||
// Take the receive right out of this transport's port set before transfer. The next owner should choose its
|
||||
// own wait set, if it needs one.
|
||||
ret = mach_port_extract_member(mach_task_self(), m_receive_port.port(), m_port_set.port());
|
||||
VERIFY(ret == KERN_SUCCESS);
|
||||
|
||||
return TransportHandle { move(m_receive_port), move(m_send_port) };
|
||||
}
|
||||
|
||||
}
|
||||
119
Libraries/LibIPC/TransportMachPort.h
Normal file
119
Libraries/LibIPC/TransportMachPort.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#if !defined(AK_OS_MACOS)
|
||||
# error "TransportMachPort is only available on macOS"
|
||||
#endif
|
||||
|
||||
#include <AK/Atomic.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibIPC/Attachment.h>
|
||||
#include <LibIPC/AutoCloseFileDescriptor.h>
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibThreading/ConditionVariable.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
|
||||
namespace IPC {
|
||||
|
||||
class TransportMachPort {
|
||||
AK_MAKE_NONCOPYABLE(TransportMachPort);
|
||||
AK_MAKE_NONMOVABLE(TransportMachPort);
|
||||
|
||||
public:
|
||||
struct Paired {
|
||||
NonnullOwnPtr<TransportMachPort> local;
|
||||
TransportHandle remote_handle;
|
||||
};
|
||||
static ErrorOr<Paired> create_paired();
|
||||
|
||||
// Bootstrap a Mach port connection over an existing Unix socket.
|
||||
// Used when the initial connection is socket-based (e.g., WebDriver).
|
||||
// Both sides must call this concurrently on the same socket.
|
||||
static ErrorOr<NonnullOwnPtr<TransportMachPort>> from_socket(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
|
||||
TransportMachPort(Core::MachPort receive_right, Core::MachPort send_right);
|
||||
~TransportMachPort();
|
||||
|
||||
void set_up_read_hook(Function<void()>);
|
||||
bool is_open() const;
|
||||
|
||||
void close();
|
||||
void close_after_sending_all_pending_messages();
|
||||
|
||||
void wait_until_readable();
|
||||
|
||||
void post_message(Vector<u8> const&, Vector<Attachment>& attachments);
|
||||
|
||||
enum class ShouldShutdown {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
struct Message {
|
||||
Vector<u8> bytes;
|
||||
Queue<Attachment> attachments;
|
||||
};
|
||||
ShouldShutdown read_as_many_messages_as_possible_without_blocking(Function<void(Message&&)>&&);
|
||||
|
||||
ErrorOr<TransportHandle> release_for_transfer();
|
||||
|
||||
private:
|
||||
static constexpr unsigned int IPC_DATA_MESSAGE_ID = 0x4950C001;
|
||||
static constexpr unsigned int IPC_WAKEUP_MESSAGE_ID = 0x4950C003;
|
||||
|
||||
struct PendingMessage {
|
||||
Vector<u8> bytes;
|
||||
Vector<Attachment> attachments;
|
||||
};
|
||||
|
||||
enum class IOThreadState {
|
||||
Running,
|
||||
SendPendingMessagesAndStop,
|
||||
Stopped,
|
||||
};
|
||||
|
||||
intptr_t io_thread_loop();
|
||||
void stop_io_thread(IOThreadState desired_state);
|
||||
void wake_io_thread();
|
||||
void notify_read_available();
|
||||
void mark_peer_eof();
|
||||
void send_mach_message(PendingMessage&);
|
||||
void process_received_message(u8* buffer);
|
||||
|
||||
Core::MachPort m_receive_port;
|
||||
Core::MachPort m_send_port;
|
||||
Core::MachPort m_port_set;
|
||||
Core::MachPort m_wakeup_receive_port;
|
||||
Core::MachPort m_wakeup_send_port;
|
||||
|
||||
Atomic<bool> m_is_open { true };
|
||||
|
||||
RefPtr<Threading::Thread> m_io_thread;
|
||||
Atomic<IOThreadState> m_io_thread_state { IOThreadState::Running };
|
||||
Atomic<bool> m_peer_eof { false };
|
||||
|
||||
Vector<PendingMessage> m_pending_send_messages;
|
||||
Threading::Mutex m_send_mutex;
|
||||
Vector<u8> m_send_buffer;
|
||||
|
||||
Threading::Mutex m_incoming_mutex;
|
||||
Threading::ConditionVariable m_incoming_cv { m_incoming_mutex };
|
||||
Vector<NonnullOwnPtr<Message>> m_incoming_messages;
|
||||
|
||||
RefPtr<AutoCloseFileDescriptor> m_notify_hook_read_fd;
|
||||
RefPtr<AutoCloseFileDescriptor> m_notify_hook_write_fd;
|
||||
RefPtr<Core::Notifier> m_read_hook_notifier;
|
||||
Function<void()> m_on_read_hook;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,11 @@
|
|||
|
||||
namespace IPC {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TransportSocket>> TransportSocket::from_socket(NonnullOwnPtr<Core::LocalSocket> socket)
|
||||
{
|
||||
return make<TransportSocket>(move(socket));
|
||||
}
|
||||
|
||||
ErrorOr<TransportSocket::Paired> TransportSocket::create_paired()
|
||||
{
|
||||
int fds[2] {};
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public:
|
|||
TransportHandle remote_handle;
|
||||
};
|
||||
static ErrorOr<Paired> create_paired();
|
||||
static ErrorOr<NonnullOwnPtr<TransportSocket>> from_socket(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
|
||||
explicit TransportSocket(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
~TransportSocket();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@
|
|||
|
||||
namespace IPC {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TransportSocketWindows>> TransportSocketWindows::from_socket(NonnullOwnPtr<Core::LocalSocket> socket)
|
||||
{
|
||||
return make<TransportSocketWindows>(move(socket));
|
||||
}
|
||||
|
||||
ErrorOr<TransportSocketWindows::Paired> TransportSocketWindows::create_paired()
|
||||
{
|
||||
int fds[2] {};
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
TransportHandle remote_handle;
|
||||
};
|
||||
static ErrorOr<Paired> create_paired();
|
||||
static ErrorOr<NonnullOwnPtr<TransportSocketWindows>> from_socket(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
|
||||
explicit TransportSocketWindows(NonnullOwnPtr<Core::LocalSocket> socket);
|
||||
|
||||
|
|
|
|||
|
|
@ -1292,15 +1292,28 @@ TransferDataEncoder::TransferDataEncoder(IPC::MessageBuffer&& buffer)
|
|||
{
|
||||
}
|
||||
|
||||
IPC::MessageBuffer const& TransferDataEncoder::buffer() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
IPC::MessageBuffer TransferDataEncoder::take_buffer() const
|
||||
{
|
||||
VERIFY(!m_buffer_has_been_taken);
|
||||
m_buffer_has_been_taken = true;
|
||||
return move(m_buffer);
|
||||
}
|
||||
|
||||
void TransferDataEncoder::append(SerializationRecord&& record)
|
||||
{
|
||||
VERIFY(!m_buffer_has_been_taken);
|
||||
MUST(m_buffer.append_data(record.data(), record.size()));
|
||||
}
|
||||
|
||||
void TransferDataEncoder::extend(Vector<TransferDataEncoder> data_holders)
|
||||
{
|
||||
for (auto& data_holder : data_holders)
|
||||
MUST(m_buffer.extend(move(data_holder.m_buffer)));
|
||||
MUST(m_buffer.extend(data_holder.take_buffer()));
|
||||
}
|
||||
|
||||
TransferDataDecoder::TransferDataDecoder(SerializationRecord const& record)
|
||||
|
|
@ -1337,12 +1350,14 @@ namespace IPC {
|
|||
template<>
|
||||
ErrorOr<void> encode(Encoder& encoder, Web::HTML::TransferDataEncoder const& data_holder)
|
||||
{
|
||||
TRY(encoder.encode(data_holder.buffer().data()));
|
||||
auto buffer = data_holder.take_buffer();
|
||||
auto data = buffer.take_data();
|
||||
auto attachments = buffer.take_attachments();
|
||||
|
||||
auto const& attachments = data_holder.buffer().attachments();
|
||||
TRY(encoder.encode(data));
|
||||
TRY(encoder.encode(static_cast<u32>(attachments.size())));
|
||||
for (auto const& attachment : attachments)
|
||||
TRY(encoder.append_attachment(TRY(attachment.clone())));
|
||||
for (auto& attachment : attachments)
|
||||
TRY(encoder.append_attachment(move(attachment)));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibIPC/Decoder.h>
|
||||
|
|
@ -29,17 +30,19 @@ public:
|
|||
template<typename T>
|
||||
void encode(T const& value)
|
||||
{
|
||||
VERIFY(!m_buffer_has_been_taken);
|
||||
MUST(m_encoder.encode(value));
|
||||
}
|
||||
|
||||
void append(SerializationRecord&&);
|
||||
void extend(Vector<TransferDataEncoder>);
|
||||
|
||||
IPC::MessageBuffer const& buffer() const { return m_buffer; }
|
||||
IPC::MessageBuffer take_buffer() { return move(m_buffer); }
|
||||
IPC::MessageBuffer const& buffer() const;
|
||||
IPC::MessageBuffer take_buffer() const;
|
||||
|
||||
private:
|
||||
IPC::MessageBuffer m_buffer;
|
||||
mutable IPC::MessageBuffer m_buffer;
|
||||
mutable bool m_buffer_has_been_taken { false };
|
||||
IPC::Encoder m_encoder;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <LibWebView/WebContentClient.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
# include <LibWebView/MachPortServer.h>
|
||||
#endif
|
||||
|
||||
|
|
@ -90,8 +91,9 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
m_mach_port_server = make<MachPortServer>();
|
||||
set_mach_server_name(m_mach_port_server->server_port_name());
|
||||
|
||||
m_mach_port_server->on_receive_child_mach_port = [this](auto pid, auto port) {
|
||||
set_process_mach_port(pid, move(port));
|
||||
m_mach_port_server->on_receive_child_mach_port = [this](MachPortServer::ChildMachPortRegistration registration) {
|
||||
set_process_mach_port(registration.pid, move(registration.child_port));
|
||||
m_transport_bootstrap_server.register_reply_port(registration.pid, move(registration.reply_port));
|
||||
};
|
||||
m_mach_port_server->on_receive_backing_stores = [](MachPortServer::BackingStoresMessage message) {
|
||||
if (auto view = WebContentClient::view_for_pid_and_page_id(message.pid, message.page_id); view.has_value())
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
#include <LibWebView/Settings.h>
|
||||
#include <LibWebView/StorageJar.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
#endif
|
||||
|
||||
namespace WebView {
|
||||
|
||||
struct ApplicationSettingsObserver;
|
||||
|
|
@ -58,6 +62,9 @@ public:
|
|||
static StorageJar& storage_jar() { return *the().m_storage_jar; }
|
||||
|
||||
static ProcessManager& process_manager() { return *the().m_process_manager; }
|
||||
#if defined(AK_OS_MACOS)
|
||||
static IPC::TransportBootstrapMachServer& transport_bootstrap_server() { return the().m_transport_bootstrap_server; }
|
||||
#endif
|
||||
|
||||
ErrorOr<NonnullRefPtr<WebContentClient>> launch_web_content_process(ViewImplementation&);
|
||||
|
||||
|
|
@ -262,6 +269,7 @@ private:
|
|||
|
||||
#if defined(AK_OS_MACOS)
|
||||
OwnPtr<MachPortServer> m_mach_port_server;
|
||||
IPC::TransportBootstrapMachServer m_transport_bootstrap_server;
|
||||
#endif
|
||||
|
||||
OwnPtr<DevTools::DevToolsServer> m_devtools;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/ConnectionToServer.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <LibWebView/BrowserProcess.h>
|
||||
#include <LibWebView/URL.h>
|
||||
|
|
@ -47,9 +48,9 @@ ErrorOr<BrowserProcess::ProcessDisposition> BrowserProcess::connect(Vector<ByteS
|
|||
|
||||
ErrorOr<void> BrowserProcess::connect_as_client(ByteString const& socket_path, Vector<ByteString> const& raw_urls, NewWindow new_window)
|
||||
{
|
||||
// TODO: Mach IPC
|
||||
auto socket = TRY(Core::LocalSocket::connect(socket_path));
|
||||
auto client = UIProcessClient::construct(make<IPC::Transport>(move(socket)));
|
||||
auto transport = TRY(IPC::Transport::from_socket(move(socket)));
|
||||
auto client = UIProcessClient::construct(move(transport));
|
||||
|
||||
switch (new_window) {
|
||||
case NewWindow::Yes:
|
||||
|
|
@ -67,14 +68,19 @@ ErrorOr<void> BrowserProcess::connect_as_client(ByteString const& socket_path, V
|
|||
|
||||
ErrorOr<void> BrowserProcess::connect_as_server(ByteString const& socket_path)
|
||||
{
|
||||
// TODO: Mach IPC
|
||||
auto socket_fd = TRY(Process::create_ipc_socket(socket_path));
|
||||
m_socket_path = socket_path;
|
||||
m_local_server = Core::LocalServer::construct();
|
||||
TRY(m_local_server->take_over_fd(socket_fd));
|
||||
|
||||
m_local_server->on_accept = [this](auto client_socket) {
|
||||
accept_transport(make<IPC::Transport>(move(client_socket)));
|
||||
auto transport = IPC::Transport::from_socket(move(client_socket));
|
||||
if (transport.is_error()) {
|
||||
dbgln("Failed to create IPC transport for UIProcess client: {}", transport.error());
|
||||
return;
|
||||
}
|
||||
|
||||
accept_transport(transport.release_value());
|
||||
};
|
||||
|
||||
return {};
|
||||
|
|
|
|||
|
|
@ -192,6 +192,11 @@ ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(Web
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (auto server = mach_server_name(); server.has_value()) {
|
||||
arguments.append("--mach-server-name"sv);
|
||||
arguments.append(server.value());
|
||||
}
|
||||
|
||||
// Propogate this process-wide setting to the child process also.
|
||||
if (URL::file_scheme_urls_have_tuple_origins())
|
||||
arguments.append("--tuple-file-origins"sv);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <AK/Debug.h>
|
||||
#include <LibCore/Platform/MachMessageTypes.h>
|
||||
#include <LibCore/Platform/ProcessStatisticsMach.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
#include <LibWebView/MachPortServer.h>
|
||||
|
||||
|
|
@ -86,21 +85,23 @@ void MachPortServer::thread_loop()
|
|||
}
|
||||
|
||||
if (message.header.msgh_id == Core::Platform::SELF_TASK_PORT_MESSAGE_ID) {
|
||||
if (MACH_MSGH_BITS_LOCAL(message.header.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND) {
|
||||
dbgln("Received message with invalid local port rights {}, ignoring", MACH_MSGH_BITS_LOCAL(message.header.msgh_bits));
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
dbgln_if(MACH_PORT_DEBUG, "Received child port {:x} from pid {}", child_port.port(), pid);
|
||||
|
||||
// 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));
|
||||
on_receive_child_mach_port({ pid, move(child_port), move(reply_port) });
|
||||
continue;
|
||||
}
|
||||
|
||||
dbgln("Received message with id {}, ignoring", message.header.msgh_id);
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,12 @@ public:
|
|||
|
||||
bool is_initialized();
|
||||
|
||||
Function<void(pid_t, Core::MachPort)> on_receive_child_mach_port;
|
||||
struct ChildMachPortRegistration {
|
||||
pid_t pid { -1 };
|
||||
Core::MachPort child_port;
|
||||
Core::MachPort reply_port;
|
||||
};
|
||||
Function<void(ChildMachPortRegistration)> on_receive_child_mach_port;
|
||||
struct BackingStoresMessage {
|
||||
pid_t pid { -1 };
|
||||
u64 page_id { 0 };
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@
|
|||
|
||||
#include <fcntl.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
# include <LibWebView/Application.h>
|
||||
#endif
|
||||
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
# include <AK/ScopeGuard.h>
|
||||
# include <AK/Windows.h>
|
||||
|
|
@ -36,20 +41,6 @@ Process::~Process()
|
|||
|
||||
ErrorOr<Process::ProcessAndIPCTransport> Process::spawn_and_connect_to_process(Core::ProcessSpawnOptions const& options, bool capture_output)
|
||||
{
|
||||
// TODO: Mach IPC
|
||||
|
||||
int socket_fds[2] {};
|
||||
TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds));
|
||||
|
||||
ArmedScopeGuard guard_fd_0 { [&] { MUST(Core::System::close(socket_fds[0])); } };
|
||||
ArmedScopeGuard guard_fd_1 { [&] { MUST(Core::System::close(socket_fds[1])); } };
|
||||
|
||||
// Note: Core::System::socketpair creates inheritable sockets both on Linux and Windows unless SOCK_CLOEXEC is specified.
|
||||
TRY(Core::System::set_close_on_exec(socket_fds[0], true));
|
||||
|
||||
auto takeover_string = MUST(String::formatted("{}:{}", options.name, socket_fds[1]));
|
||||
TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes));
|
||||
|
||||
// Set up pipes for stdout/stderr capture if requested
|
||||
ProcessOutputCapture output_capture;
|
||||
Array<int, 2> stdout_pipe {};
|
||||
|
|
@ -72,8 +63,39 @@ ErrorOr<Process::ProcessAndIPCTransport> Process::spawn_and_connect_to_process(C
|
|||
spawn_options.file_actions.append(Core::FileAction::CloseFile { .fd = stderr_pipe[1] });
|
||||
}
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
auto port_a_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto port_a_send = TRY(port_a_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
auto port_b_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
||||
auto port_b_send = TRY(port_b_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
||||
|
||||
auto process = TRY(Core::Process::spawn(spawn_options));
|
||||
|
||||
Application::transport_bootstrap_server().register_pending_transport(process.pid(), IPC::TransportBootstrapMachPorts { move(port_b_recv), move(port_a_send) });
|
||||
|
||||
auto transport = make<IPC::Transport>(move(port_a_recv), move(port_b_send));
|
||||
#else
|
||||
int socket_fds[2] {};
|
||||
TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds));
|
||||
|
||||
ArmedScopeGuard guard_fd_0 { [&] { MUST(Core::System::close(socket_fds[0])); } };
|
||||
ArmedScopeGuard guard_fd_1 { [&] { MUST(Core::System::close(socket_fds[1])); } };
|
||||
|
||||
// Note: Core::System::socketpair creates inheritable sockets both on Linux and Windows unless SOCK_CLOEXEC is specified.
|
||||
TRY(Core::System::set_close_on_exec(socket_fds[0], true));
|
||||
|
||||
auto takeover_string = MUST(String::formatted("{}:{}", options.name, socket_fds[1]));
|
||||
TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes));
|
||||
|
||||
auto process = TRY(Core::Process::spawn(spawn_options));
|
||||
|
||||
auto ipc_socket = TRY(Core::LocalSocket::adopt_fd(socket_fds[0]));
|
||||
guard_fd_0.disarm();
|
||||
TRY(ipc_socket->set_blocking(true));
|
||||
|
||||
auto transport = make<IPC::Transport>(move(ipc_socket));
|
||||
#endif
|
||||
|
||||
if (capture_output) {
|
||||
// Close write ends in parent
|
||||
MUST(Core::System::close(stdout_pipe[1]));
|
||||
|
|
@ -84,11 +106,7 @@ ErrorOr<Process::ProcessAndIPCTransport> Process::spawn_and_connect_to_process(C
|
|||
output_capture.stderr_file = TRY(Core::File::adopt_fd(stderr_pipe[0], Core::File::OpenMode::Read));
|
||||
}
|
||||
|
||||
auto ipc_socket = TRY(Core::LocalSocket::adopt_fd(socket_fds[0]));
|
||||
guard_fd_0.disarm();
|
||||
TRY(ipc_socket->set_blocking(true));
|
||||
|
||||
return ProcessAndIPCTransport { move(process), make<IPC::Transport>(move(ipc_socket)), move(output_capture) };
|
||||
return ProcessAndIPCTransport { move(process), move(transport), move(output_capture) };
|
||||
}
|
||||
|
||||
ErrorOr<Optional<pid_t>> Process::get_process_pid(StringView process_name, StringView pid_path)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@
|
|||
#include <LibIPC/SingleServer.h>
|
||||
#include <LibMain/Main.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibCore/Platform/ProcessStatisticsMach.h>
|
||||
#endif
|
||||
|
||||
ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
{
|
||||
AK::set_rich_debug_enabled(true);
|
||||
|
|
@ -34,12 +30,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
|
||||
Core::EventLoop event_loop;
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
if (!mach_server_name.is_empty())
|
||||
Core::Platform::register_with_mach_server(mach_server_name);
|
||||
#endif
|
||||
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<ImageDecoder::ConnectionFromClient>());
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<ImageDecoder::ConnectionFromClient>(mach_server_name));
|
||||
|
||||
return event_loop.exec();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@
|
|||
#include <RequestServer/Resolver.h>
|
||||
#include <RequestServer/ResourceSubstitutionMap.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibCore/Platform/ProcessStatisticsMach.h>
|
||||
#endif
|
||||
|
||||
namespace RequestServer {
|
||||
|
||||
OwnPtr<ResourceSubstitutionMap> g_resource_substitution_map;
|
||||
|
|
@ -82,11 +78,6 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
Core::EventLoop::register_signal(SIGTERM, handle_signal);
|
||||
#endif
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
if (!mach_server_name.is_empty())
|
||||
Core::Platform::register_with_mach_server(mach_server_name);
|
||||
#endif
|
||||
|
||||
Optional<HTTP::DiskCache> disk_cache;
|
||||
|
||||
if (http_disk_cache_mode != "disabled"sv) {
|
||||
|
|
@ -111,6 +102,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
RequestServer::ConnectionFromClient::ConnectionMap connections;
|
||||
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<RequestServer::ConnectionFromClient>(
|
||||
mach_server_name,
|
||||
RequestServer::ConnectionFromClient::IsPrimaryConnection::Yes, connections, disk_cache));
|
||||
|
||||
return event_loop.exec();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@
|
|||
#include <AK/Time.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibHTTP/Cookie/Cookie.h>
|
||||
#include <LibHTTP/Cookie/ParsedCookie.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibURL/Parser.h>
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
|
|
@ -194,8 +196,6 @@ static bool fire_an_event(FlyString const& name, Optional<Web::DOM::Element&> ta
|
|||
|
||||
ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::PageClient& page_client, ByteString const& webdriver_ipc_path)
|
||||
{
|
||||
// TODO: Mach IPC and Windows IPC
|
||||
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Trying to connect to {}", webdriver_ipc_path);
|
||||
auto socket = TRY(Core::LocalSocket::connect(webdriver_ipc_path));
|
||||
|
||||
|
|
@ -203,7 +203,8 @@ ErrorOr<NonnullRefPtr<WebDriverConnection>> WebDriverConnection::connect(Web::Pa
|
|||
page_client.page().set_should_block_pop_ups(false);
|
||||
|
||||
dbgln_if(WEBDRIVER_DEBUG, "Connected to WebDriver");
|
||||
auto connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(make<IPC::Transport>(move(socket)), page_client)));
|
||||
auto transport = TRY(IPC::Transport::from_socket(move(socket)));
|
||||
auto connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) WebDriverConnection(move(transport), page_client)));
|
||||
connection->async_did_set_window_handle(page_client.page().top_level_traversable()->window_handle());
|
||||
return connection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include <LibCore/Process.h>
|
||||
#include <LibCore/Resource.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/SystemServerTakeover.h>
|
||||
#include <LibCrypto/OpenSSLForward.h>
|
||||
#include <LibGfx/Font/FontDatabase.h>
|
||||
#include <LibGfx/Font/PathFontProvider.h>
|
||||
|
|
@ -45,7 +44,10 @@
|
|||
#include <openssl/thread.h>
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
# include <LibCore/Platform/ProcessStatisticsMach.h>
|
||||
# include <LibIPC/Transport.h>
|
||||
# include <LibIPC/TransportBootstrapMach.h>
|
||||
#else
|
||||
# include <LibIPC/SingleServer.h>
|
||||
#endif
|
||||
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
|
|
@ -223,13 +225,6 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
#if defined(AK_OS_MACOS)
|
||||
if (!mach_server_name.is_empty()) {
|
||||
auto server_port = Core::Platform::register_with_mach_server(mach_server_name);
|
||||
Web::Painting::BackingStoreManager::set_browser_mach_port(move(server_port));
|
||||
}
|
||||
#endif
|
||||
|
||||
OPENSSL_TRY(OSSL_set_max_threads(nullptr, Core::System::hardware_concurrency()));
|
||||
|
||||
Web::HTML::Window::set_enable_test_mode(enable_test_mode);
|
||||
|
|
@ -255,8 +250,15 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
if (maybe_content_filter_error.is_error())
|
||||
dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
|
||||
|
||||
auto webcontent_socket = TRY(Core::take_over_socket_from_system_server("WebContent"sv));
|
||||
auto webcontent_client = WebContent::ConnectionFromClient::construct(make<IPC::Transport>(move(webcontent_socket)));
|
||||
#if defined(AK_OS_MACOS)
|
||||
auto browser_port = TRY(Core::MachPort::look_up_from_bootstrap_server(ByteString { mach_server_name }));
|
||||
auto transport_ports = TRY(IPC::bootstrap_transport_from_server_port(browser_port));
|
||||
Web::Painting::BackingStoreManager::set_browser_mach_port(move(browser_port));
|
||||
auto webcontent_client = WebContent::ConnectionFromClient::construct(
|
||||
make<IPC::Transport>(move(transport_ports.receive_right), move(transport_ports.send_right)));
|
||||
#else
|
||||
auto webcontent_client = TRY(IPC::take_over_accepted_client_from_system_server<WebContent::ConnectionFromClient>(mach_server_name));
|
||||
#endif
|
||||
|
||||
auto& heap = Web::Bindings::main_thread_vm().heap();
|
||||
webcontent_client->on_request_server_connection = [&heap](auto const& handle) {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/WebDriver/Proxy.h>
|
||||
#include <LibWeb/WebDriver/TimeoutsConfiguration.h>
|
||||
|
|
@ -202,6 +204,8 @@ ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<S
|
|||
{
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
static_assert(IsSame<IPC::Transport, IPC::TransportSocketWindows>, "Need to handle other IPC transports here");
|
||||
#elif defined(AK_OS_MACOS)
|
||||
static_assert(IsSame<IPC::Transport, IPC::TransportMachPort>, "Need to handle other IPC transports here");
|
||||
#else
|
||||
static_assert(IsSame<IPC::Transport, IPC::TransportSocket>, "Need to handle other IPC transports here");
|
||||
#endif
|
||||
|
|
@ -214,7 +218,12 @@ ErrorOr<NonnullRefPtr<Core::LocalServer>> Session::create_server(NonnullRefPtr<S
|
|||
server->listen(*m_web_content_socket_path);
|
||||
|
||||
server->on_accept = [this, promise](auto client_socket) {
|
||||
auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(make<IPC::Transport>(move(client_socket))));
|
||||
auto maybe_transport = IPC::Transport::from_socket(move(client_socket));
|
||||
if (maybe_transport.is_error()) {
|
||||
promise->resolve(maybe_transport.release_error());
|
||||
return;
|
||||
}
|
||||
auto maybe_connection = adopt_nonnull_ref_or_enomem(new (nothrow) WebContentConnection(maybe_transport.release_value()));
|
||||
if (maybe_connection.is_error()) {
|
||||
promise->resolve(maybe_connection.release_error());
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibCrypto/OpenSSLForward.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibIPC/SingleServer.h>
|
||||
#include <LibIPC/Transport.h>
|
||||
#include <LibIPC/TransportHandle.h>
|
||||
#include <LibImageDecoderClient/Client.h>
|
||||
#include <LibMain/Main.h>
|
||||
|
|
@ -49,6 +50,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
|
||||
StringView serenity_resource_root;
|
||||
StringView worker_type_string;
|
||||
StringView mach_server_name;
|
||||
Vector<ByteString> certificates;
|
||||
bool expose_experimental_interfaces = false;
|
||||
bool enable_http_memory_cache = false;
|
||||
|
|
@ -62,6 +64,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
args_parser.add_option(enable_http_memory_cache, "Enable HTTP cache", "enable-http-memory-cache");
|
||||
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
|
||||
args_parser.add_option(worker_type_string, "Type of WebWorker to start (dedicated, shared, or service)", "type", 't', "type");
|
||||
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
|
||||
args_parser.add_option(file_origins_are_tuple_origins, "Treat file:// URLs as having tuple origins", "tuple-file-origins");
|
||||
|
||||
args_parser.parse(arguments);
|
||||
|
|
@ -91,7 +94,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
|||
|
||||
Web::Bindings::initialize_main_thread_vm(worker_type);
|
||||
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<WebWorker::ConnectionFromClient>());
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<WebWorker::ConnectionFromClient>(mach_server_name));
|
||||
|
||||
auto& heap = Web::Bindings::main_thread_vm().heap();
|
||||
client->on_request_server_connection = [&heap](auto const& handle) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
if (NOT WIN32)
|
||||
if (UNIX AND NOT APPLE)
|
||||
ladybird_test("TestTransportSocket.cpp" LibIPC LIBS LibIPC)
|
||||
endif()
|
||||
|
|
|
|||
Loading…
Reference in a new issue