diff --git a/Libraries/LibCore/MachPort.h b/Libraries/LibCore/MachPort.h index a8f5b63fb3..426478ed9a 100644 --- a/Libraries/LibCore/MachPort.h +++ b/Libraries/LibCore/MachPort.h @@ -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); } diff --git a/Libraries/LibCore/Platform/MachMessageTypes.h b/Libraries/LibCore/Platform/MachMessageTypes.h index 14312c38ce..8c8e81ec77 100644 --- a/Libraries/LibCore/Platform/MachMessageTypes.h +++ b/Libraries/LibCore/Platform/MachMessageTypes.h @@ -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; } diff --git a/Libraries/LibCore/Platform/ProcessStatisticsMach.cpp b/Libraries/LibCore/Platform/ProcessStatisticsMach.cpp index b40d9d5e80..3d5baaa82b 100644 --- a/Libraries/LibCore/Platform/ProcessStatisticsMach.cpp +++ b/Libraries/LibCore/Platform/ProcessStatisticsMach.cpp @@ -10,10 +10,7 @@ # error "This file is only available on Mach platforms" #endif -#include #include -#include -#include #include namespace Core::Platform { @@ -76,36 +73,4 @@ ErrorOr 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; -} - } diff --git a/Libraries/LibCore/Platform/ProcessStatisticsMach.h b/Libraries/LibCore/Platform/ProcessStatisticsMach.h index b6c0337be7..71fe6e29f8 100644 --- a/Libraries/LibCore/Platform/ProcessStatisticsMach.h +++ b/Libraries/LibCore/Platform/ProcessStatisticsMach.h @@ -14,10 +14,7 @@ #include #include -#include namespace Core::Platform { -CORE_API MachPort register_with_mach_server(ByteString const& server_name); - } diff --git a/Libraries/LibIPC/Attachment.cpp b/Libraries/LibIPC/Attachment.cpp index ddc5908ba7..aa7fdd230f 100644 --- a/Libraries/LibIPC/Attachment.cpp +++ b/Libraries/LibIPC/Attachment.cpp @@ -37,12 +37,6 @@ Attachment Attachment::from_fd(int fd) return attachment; } -ErrorOr 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); diff --git a/Libraries/LibIPC/Attachment.h b/Libraries/LibIPC/Attachment.h index 5c54945246..292c52c48a 100644 --- a/Libraries/LibIPC/Attachment.h +++ b/Libraries/LibIPC/Attachment.h @@ -8,6 +8,11 @@ #include #include +#include + +#if defined(AK_OS_MACOS) +# include +#endif namespace IPC { @@ -21,11 +26,22 @@ public: ~Attachment(); static Attachment from_fd(int fd); - ErrorOr 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 }; } diff --git a/Libraries/LibIPC/AttachmentMachPort.cpp b/Libraries/LibIPC/AttachmentMachPort.cpp new file mode 100644 index 0000000000..b0388c49f8 --- /dev/null +++ b/Libraries/LibIPC/AttachmentMachPort.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +// 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); +} + +} diff --git a/Libraries/LibIPC/CMakeLists.txt b/Libraries/LibIPC/CMakeLists.txt index a7bac491d3..2c2f57ad25 100644 --- a/Libraries/LibIPC/CMakeLists.txt +++ b/Libraries/LibIPC/CMakeLists.txt @@ -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 diff --git a/Libraries/LibIPC/Forward.h b/Libraries/LibIPC/Forward.h index 63be2a49b0..157b21819a 100644 --- a/Libraries/LibIPC/Forward.h +++ b/Libraries/LibIPC/Forward.h @@ -7,6 +7,7 @@ #pragma once #include +#include 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 ErrorOr encode(Encoder&, T const&); diff --git a/Libraries/LibIPC/SingleServer.h b/Libraries/LibIPC/SingleServer.h index 0f950ff4e8..41e1c79ae3 100644 --- a/Libraries/LibIPC/SingleServer.h +++ b/Libraries/LibIPC/SingleServer.h @@ -6,16 +6,30 @@ #pragma once -#include +#include #include +#include + +#if defined(AK_OS_MACOS) +# include +#else +# include +#endif namespace IPC { template -ErrorOr> take_over_accepted_client_from_system_server(Args&&... args) +ErrorOr> 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( + make(move(ports.receive_right), move(ports.send_right)), + forward(args)...); +#else auto socket = TRY(Core::take_over_socket_from_system_server()); return IPC::new_client_connection(make(move(socket)), forward(args)...); +#endif } } diff --git a/Libraries/LibIPC/Transport.h b/Libraries/LibIPC/Transport.h index 9862d41849..49c66b30e8 100644 --- a/Libraries/LibIPC/Transport.h +++ b/Libraries/LibIPC/Transport.h @@ -7,8 +7,11 @@ #pragma once #include +#include -#if !defined(AK_OS_WINDOWS) +#if defined(AK_OS_MACOS) +# include +#elif !defined(AK_OS_WINDOWS) # include #else # include @@ -16,11 +19,4 @@ namespace IPC { -#if !defined(AK_OS_WINDOWS) -// Unix Domain Sockets -using Transport = TransportSocket; -#else -using Transport = TransportSocketWindows; -#endif - } diff --git a/Libraries/LibIPC/TransportBootstrapMach.cpp b/Libraries/LibIPC/TransportBootstrapMach.cpp new file mode 100644 index 0000000000..4d11841471 --- /dev/null +++ b/Libraries/LibIPC/TransportBootstrapMach.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace IPC { + +static ErrorOr 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 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 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 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 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(name_bytes.size()); + TRY(write_exact(socket_fd, ReadonlyBytes { reinterpret_cast(&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(&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(); + }); +} + +} diff --git a/Libraries/LibIPC/TransportBootstrapMach.h b/Libraries/LibIPC/TransportBootstrapMach.h new file mode 100644 index 0000000000..e3687fc628 --- /dev/null +++ b/Libraries/LibIPC/TransportBootstrapMach.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +#if !defined(AK_OS_MACH) +# error "TransportBootstrapMach is only available on Mach platforms" +#endif + +#include +#include +#include + +namespace IPC { + +struct TransportBootstrapMachPorts { + Core::MachPort receive_right; + Core::MachPort send_right; +}; + +ErrorOr bootstrap_transport_from_mach_server(StringView server_name); +ErrorOr bootstrap_transport_from_server_port(Core::MachPort const& server_port); +ErrorOr 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; + + static void send_transport_ports_to_child(Core::MachPort reply_port, TransportBootstrapMachPorts ports); + + Threading::Mutex m_pending_bootstrap_mutex; + HashMap m_pending_bootstrap; +}; + +} diff --git a/Libraries/LibIPC/TransportHandle.cpp b/Libraries/LibIPC/TransportHandle.cpp index 88ac5b9f49..d15e2363c2 100644 --- a/Libraries/LibIPC/TransportHandle.cpp +++ b/Libraries/LibIPC/TransportHandle.cpp @@ -5,14 +5,57 @@ */ #include +#include #include #include -#include #include #include 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> TransportHandle::create_transport() const +{ + VERIFY(MACH_PORT_VALID(m_receive_right.port())); + VERIFY(MACH_PORT_VALID(m_send_right.port())); + return make(move(m_receive_right), move(m_send_right)); +} + +template<> +ErrorOr 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 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 decode(Decoder& decoder) return TransportHandle { move(file) }; } +#endif + } diff --git a/Libraries/LibIPC/TransportHandle.h b/Libraries/LibIPC/TransportHandle.h index 83708cf4d3..9192bcfe4e 100644 --- a/Libraries/LibIPC/TransportHandle.h +++ b/Libraries/LibIPC/TransportHandle.h @@ -13,16 +13,12 @@ #include #include -namespace IPC { - -#if !defined(AK_OS_WINDOWS) -class TransportSocket; -using Transport = TransportSocket; -#else -class TransportSocketWindows; -using Transport = TransportSocketWindows; +#if defined(AK_OS_MACOS) +# include #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> 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 friend ErrorOr encode(Encoder&, U const&); template friend ErrorOr decode(Decoder&); - - mutable File m_file; }; } diff --git a/Libraries/LibIPC/TransportMachPort.cpp b/Libraries/LibIPC/TransportMachPort.cpp new file mode 100644 index 0000000000..0c030df49f --- /dev/null +++ b/Libraries/LibIPC/TransportMachPort.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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(&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> TransportMachPort::from_socket(NonnullOwnPtr socket) +{ + auto ports = TRY(bootstrap_transport_over_socket(*socket)); + return make(move(ports.receive_right), move(ports.send_right)); +} + +ErrorOr 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(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 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(); + buffer.resize(RECV_BUFFER_SIZE); + + for (;;) { + if (m_io_thread_state.load() == IOThreadState::Stopped) + break; + + Vector 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(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(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(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(header + 1); + body->msgh_descriptor_count = total_desc_count; + + auto* desc_ptr = reinterpret_cast(body + 1); + for (size_t i = 0; i < port_count; ++i) { + auto disposition = static_cast(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(&desc_ptr[port_count]); + ool_desc->address = const_cast(static_cast(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(buffer); + VERIFY(header->msgh_bits & MACH_MSGH_BITS_COMPLEX); + auto* body = reinterpret_cast(header + 1); + VERIFY(body->msgh_descriptor_count > 0); + auto attachment_count = body->msgh_descriptor_count - 1; + auto* descriptors = reinterpret_cast(body + 1); + auto const* payload = reinterpret_cast(&descriptors[attachment_count]); + + auto message = make(); + 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(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(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 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 const& bytes, Vector& 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&& callback) +{ + Vector> 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 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) }; +} + +} diff --git a/Libraries/LibIPC/TransportMachPort.h b/Libraries/LibIPC/TransportMachPort.h new file mode 100644 index 0000000000..a624e22776 --- /dev/null +++ b/Libraries/LibIPC/TransportMachPort.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026, Aliaksandr Kalenik + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#if !defined(AK_OS_MACOS) +# error "TransportMachPort is only available on macOS" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace IPC { + +class TransportMachPort { + AK_MAKE_NONCOPYABLE(TransportMachPort); + AK_MAKE_NONMOVABLE(TransportMachPort); + +public: + struct Paired { + NonnullOwnPtr local; + TransportHandle remote_handle; + }; + static ErrorOr 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> from_socket(NonnullOwnPtr socket); + + TransportMachPort(Core::MachPort receive_right, Core::MachPort send_right); + ~TransportMachPort(); + + void set_up_read_hook(Function); + bool is_open() const; + + void close(); + void close_after_sending_all_pending_messages(); + + void wait_until_readable(); + + void post_message(Vector const&, Vector& attachments); + + enum class ShouldShutdown { + No, + Yes, + }; + struct Message { + Vector bytes; + Queue attachments; + }; + ShouldShutdown read_as_many_messages_as_possible_without_blocking(Function&&); + + ErrorOr 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 bytes; + Vector 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 m_is_open { true }; + + RefPtr m_io_thread; + Atomic m_io_thread_state { IOThreadState::Running }; + Atomic m_peer_eof { false }; + + Vector m_pending_send_messages; + Threading::Mutex m_send_mutex; + Vector m_send_buffer; + + Threading::Mutex m_incoming_mutex; + Threading::ConditionVariable m_incoming_cv { m_incoming_mutex }; + Vector> m_incoming_messages; + + RefPtr m_notify_hook_read_fd; + RefPtr m_notify_hook_write_fd; + RefPtr m_read_hook_notifier; + Function m_on_read_hook; +}; + +} diff --git a/Libraries/LibIPC/TransportSocket.cpp b/Libraries/LibIPC/TransportSocket.cpp index c9c4ac65ee..1f64648e1b 100644 --- a/Libraries/LibIPC/TransportSocket.cpp +++ b/Libraries/LibIPC/TransportSocket.cpp @@ -20,6 +20,11 @@ namespace IPC { +ErrorOr> TransportSocket::from_socket(NonnullOwnPtr socket) +{ + return make(move(socket)); +} + ErrorOr TransportSocket::create_paired() { int fds[2] {}; diff --git a/Libraries/LibIPC/TransportSocket.h b/Libraries/LibIPC/TransportSocket.h index 39fb3f4073..095f1e75b0 100644 --- a/Libraries/LibIPC/TransportSocket.h +++ b/Libraries/LibIPC/TransportSocket.h @@ -46,6 +46,7 @@ public: TransportHandle remote_handle; }; static ErrorOr create_paired(); + static ErrorOr> from_socket(NonnullOwnPtr socket); explicit TransportSocket(NonnullOwnPtr socket); ~TransportSocket(); diff --git a/Libraries/LibIPC/TransportSocketWindows.cpp b/Libraries/LibIPC/TransportSocketWindows.cpp index 236b1d1c8b..7eee4598aa 100644 --- a/Libraries/LibIPC/TransportSocketWindows.cpp +++ b/Libraries/LibIPC/TransportSocketWindows.cpp @@ -20,6 +20,11 @@ namespace IPC { +ErrorOr> TransportSocketWindows::from_socket(NonnullOwnPtr socket) +{ + return make(move(socket)); +} + ErrorOr TransportSocketWindows::create_paired() { int fds[2] {}; diff --git a/Libraries/LibIPC/TransportSocketWindows.h b/Libraries/LibIPC/TransportSocketWindows.h index 0841fa6d1e..e411fdf1d8 100644 --- a/Libraries/LibIPC/TransportSocketWindows.h +++ b/Libraries/LibIPC/TransportSocketWindows.h @@ -24,6 +24,7 @@ public: TransportHandle remote_handle; }; static ErrorOr create_paired(); + static ErrorOr> from_socket(NonnullOwnPtr socket); explicit TransportSocketWindows(NonnullOwnPtr socket); diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index c4e2c6b688..81ccbd8e10 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -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 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 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(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 {}; } diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.h b/Libraries/LibWeb/HTML/StructuredSerialize.h index 1e8049182b..cff22c114d 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -29,17 +30,19 @@ public: template void encode(T const& value) { + VERIFY(!m_buffer_has_been_taken); MUST(m_encoder.encode(value)); } void append(SerializationRecord&&); void extend(Vector); - 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; }; diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 6b3f99bd0c..16859827fb 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -27,6 +27,7 @@ #include #if defined(AK_OS_MACOS) +# include # include #endif @@ -90,8 +91,9 @@ ErrorOr Application::initialize(Main::Arguments const& arguments) m_mach_port_server = make(); 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()) diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index a8ecbbe821..a5355ba16d 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -31,6 +31,10 @@ #include #include +#if defined(AK_OS_MACOS) +# include +#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> launch_web_content_process(ViewImplementation&); @@ -262,6 +269,7 @@ private: #if defined(AK_OS_MACOS) OwnPtr m_mach_port_server; + IPC::TransportBootstrapMachServer m_transport_bootstrap_server; #endif OwnPtr m_devtools; diff --git a/Libraries/LibWebView/BrowserProcess.cpp b/Libraries/LibWebView/BrowserProcess.cpp index b772086a61..152d8b3edb 100644 --- a/Libraries/LibWebView/BrowserProcess.cpp +++ b/Libraries/LibWebView/BrowserProcess.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -47,9 +48,9 @@ ErrorOr BrowserProcess::connect(Vector BrowserProcess::connect_as_client(ByteString const& socket_path, Vector const& raw_urls, NewWindow new_window) { - // TODO: Mach IPC auto socket = TRY(Core::LocalSocket::connect(socket_path)); - auto client = UIProcessClient::construct(make(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 BrowserProcess::connect_as_client(ByteString const& socket_path, V ErrorOr 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(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 {}; diff --git a/Libraries/LibWebView/HelperProcess.cpp b/Libraries/LibWebView/HelperProcess.cpp index 066130c7d3..a9ae23fe9e 100644 --- a/Libraries/LibWebView/HelperProcess.cpp +++ b/Libraries/LibWebView/HelperProcess.cpp @@ -192,6 +192,11 @@ ErrorOr> 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); diff --git a/Libraries/LibWebView/MachPortServer.cpp b/Libraries/LibWebView/MachPortServer.cpp index 67ef8efda2..e5495ba379 100644 --- a/Libraries/LibWebView/MachPortServer.cpp +++ b/Libraries/LibWebView/MachPortServer.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -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(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(); } } diff --git a/Libraries/LibWebView/MachPortServer.h b/Libraries/LibWebView/MachPortServer.h index da57734641..6dbc753e33 100644 --- a/Libraries/LibWebView/MachPortServer.h +++ b/Libraries/LibWebView/MachPortServer.h @@ -30,7 +30,12 @@ public: bool is_initialized(); - Function on_receive_child_mach_port; + struct ChildMachPortRegistration { + pid_t pid { -1 }; + Core::MachPort child_port; + Core::MachPort reply_port; + }; + Function on_receive_child_mach_port; struct BackingStoresMessage { pid_t pid { -1 }; u64 page_id { 0 }; diff --git a/Libraries/LibWebView/Process.cpp b/Libraries/LibWebView/Process.cpp index fdd361b367..11c221f797 100644 --- a/Libraries/LibWebView/Process.cpp +++ b/Libraries/LibWebView/Process.cpp @@ -14,6 +14,11 @@ #include +#if defined(AK_OS_MACOS) +# include +# include +#endif + #if defined(AK_OS_WINDOWS) # include # include @@ -36,20 +41,6 @@ Process::~Process() ErrorOr 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 stdout_pipe {}; @@ -72,8 +63,39 @@ ErrorOr 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(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(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::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(move(ipc_socket)), move(output_capture) }; + return ProcessAndIPCTransport { move(process), move(transport), move(output_capture) }; } ErrorOr> Process::get_process_pid(StringView process_name, StringView pid_path) diff --git a/Services/ImageDecoder/main.cpp b/Services/ImageDecoder/main.cpp index cf54248ff6..c911e9f4f0 100644 --- a/Services/ImageDecoder/main.cpp +++ b/Services/ImageDecoder/main.cpp @@ -13,10 +13,6 @@ #include #include -#if defined(AK_OS_MACOS) -# include -#endif - ErrorOr ladybird_main(Main::Arguments arguments) { AK::set_rich_debug_enabled(true); @@ -34,12 +30,7 @@ ErrorOr 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()); + auto client = TRY(IPC::take_over_accepted_client_from_system_server(mach_server_name)); return event_loop.exec(); } diff --git a/Services/RequestServer/main.cpp b/Services/RequestServer/main.cpp index 599ed240b6..e9f316f868 100644 --- a/Services/RequestServer/main.cpp +++ b/Services/RequestServer/main.cpp @@ -20,10 +20,6 @@ #include #include -#if defined(AK_OS_MACOS) -# include -#endif - namespace RequestServer { OwnPtr g_resource_substitution_map; @@ -82,11 +78,6 @@ ErrorOr 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 disk_cache; if (http_disk_cache_mode != "disabled"sv) { @@ -111,6 +102,7 @@ ErrorOr ladybird_main(Main::Arguments arguments) RequestServer::ConnectionFromClient::ConnectionMap connections; auto client = TRY(IPC::take_over_accepted_client_from_system_server( + mach_server_name, RequestServer::ConnectionFromClient::IsPrimaryConnection::Yes, connections, disk_cache)); return event_loop.exec(); diff --git a/Services/WebContent/WebDriverConnection.cpp b/Services/WebContent/WebDriverConnection.cpp index 7f55ca775b..391e69466b 100644 --- a/Services/WebContent/WebDriverConnection.cpp +++ b/Services/WebContent/WebDriverConnection.cpp @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -194,8 +196,6 @@ static bool fire_an_event(FlyString const& name, Optional ta ErrorOr> 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> 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(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; } diff --git a/Services/WebContent/main.cpp b/Services/WebContent/main.cpp index ab54d627e3..17518473d6 100644 --- a/Services/WebContent/main.cpp +++ b/Services/WebContent/main.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -45,7 +44,10 @@ #include #if defined(AK_OS_MACOS) -# include +# include +# include +#else +# include #endif #if defined(AK_OS_WINDOWS) @@ -223,13 +225,6 @@ ErrorOr 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 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(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(move(transport_ports.receive_right), move(transport_ports.send_right))); +#else + auto webcontent_client = TRY(IPC::take_over_accepted_client_from_system_server(mach_server_name)); +#endif auto& heap = Web::Bindings::main_thread_vm().heap(); webcontent_client->on_request_server_connection = [&heap](auto const& handle) { diff --git a/Services/WebDriver/Session.cpp b/Services/WebDriver/Session.cpp index 1f5952d164..07a00983a9 100644 --- a/Services/WebDriver/Session.cpp +++ b/Services/WebDriver/Session.cpp @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -202,6 +204,8 @@ ErrorOr> Session::create_server(NonnullRefPtr, "Need to handle other IPC transports here"); +#elif defined(AK_OS_MACOS) + static_assert(IsSame, "Need to handle other IPC transports here"); #else static_assert(IsSame, "Need to handle other IPC transports here"); #endif @@ -214,7 +218,12 @@ ErrorOr> Session::create_server(NonnullRefPtrlisten(*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(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; diff --git a/Services/WebWorker/main.cpp b/Services/WebWorker/main.cpp index 990957dff4..9108f0020b 100644 --- a/Services/WebWorker/main.cpp +++ b/Services/WebWorker/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ ErrorOr ladybird_main(Main::Arguments arguments) StringView serenity_resource_root; StringView worker_type_string; + StringView mach_server_name; Vector certificates; bool expose_experimental_interfaces = false; bool enable_http_memory_cache = false; @@ -62,6 +64,7 @@ ErrorOr 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 ladybird_main(Main::Arguments arguments) Web::Bindings::initialize_main_thread_vm(worker_type); - auto client = TRY(IPC::take_over_accepted_client_from_system_server()); + auto client = TRY(IPC::take_over_accepted_client_from_system_server(mach_server_name)); auto& heap = Web::Bindings::main_thread_vm().heap(); client->on_request_server_connection = [&heap](auto const& handle) { diff --git a/Tests/LibIPC/CMakeLists.txt b/Tests/LibIPC/CMakeLists.txt index 894e8859f3..7dfdac668a 100644 --- a/Tests/LibIPC/CMakeLists.txt +++ b/Tests/LibIPC/CMakeLists.txt @@ -1,3 +1,3 @@ -if (NOT WIN32) +if (UNIX AND NOT APPLE) ladybird_test("TestTransportSocket.cpp" LibIPC LIBS LibIPC) endif()