diff --git a/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn index 1abe163fd1..b5deeb9c71 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn @@ -116,6 +116,9 @@ source_set("sources") { "LocalServer.h", ] } + if (current_os == "mac") { + sources += [ "MachPort.cpp" ] + } } source_set("filewatcher") { diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index 6b9305689f..0c2a161ceb 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -69,6 +69,10 @@ else() list(APPEND SOURCES FileWatcherUnimplemented.cpp) endif() +if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "GNU") + list(APPEND SOURCES MachPort.cpp) +endif() + serenity_lib(LibCore core) target_link_libraries(LibCore PRIVATE LibCrypt LibSystem LibTimeZone LibURL) target_link_libraries(LibCore PUBLIC LibCoreMinimal) diff --git a/Userland/Libraries/LibCore/MachPort.cpp b/Userland/Libraries/LibCore/MachPort.cpp new file mode 100644 index 0000000000..a221f8f79a --- /dev/null +++ b/Userland/Libraries/LibCore/MachPort.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#if defined(AK_OS_MACOS) +# include +#endif + +namespace Core { + +static constexpr MachPort::PortRight associated_port_right(MachPort::MessageRight right) +{ + switch (right) { + case MachPort::MessageRight::MoveReceive: + case MachPort::MessageRight::CopyReceive: + case MachPort::MessageRight::DisposeReceive: + return MachPort::PortRight::Receive; + case MachPort::MessageRight::MoveSend: + case MachPort::MessageRight::CopySend: + case MachPort::MessageRight::MakeSend: + case MachPort::MessageRight::DisposeSend: + return MachPort::PortRight::Send; + case MachPort::MessageRight::MoveSendOnce: + case MachPort::MessageRight::MakeSendOnce: + case MachPort::MessageRight::DisposeSendOnce: + return MachPort::PortRight::SendOnce; + } + VERIFY_NOT_REACHED(); +} + +Error mach_error_to_error(kern_return_t error) +{ + char const* err_string = mach_error_string(error); + StringView const err_view(err_string, strlen(err_string)); + return Error::from_string_view(err_view); +} + +static Error bootstrap_error_to_error(kern_return_t error) +{ + char const* err_string = bootstrap_strerror(error); + StringView const err_view(err_string, strlen(err_string)); + return Error::from_string_view(err_view); +} + +MachPort::MachPort(PortRight right, mach_port_t port) + : m_right(right) + , m_port(port) +{ +} + +MachPort::~MachPort() +{ + unref_port(); +} + +MachPort::MachPort(MachPort&& other) + : m_right(other.m_right) + , m_port(other.release()) +{ +} + +MachPort& MachPort::operator=(MachPort&& other) +{ + if (this != &other) { + unref_port(); + m_right = other.m_right; + m_port = other.release(); + } + return *this; +} + +void MachPort::unref_port() +{ + if (!MACH_PORT_VALID(m_port)) + return; + + kern_return_t res = KERN_FAILURE; + switch (m_right) { + case PortRight::Send: + case PortRight::SendOnce: + case PortRight::DeadName: + res = mach_port_deallocate(mach_task_self(), m_port); + break; + case PortRight::Receive: + case PortRight::PortSet: + res = mach_port_mod_refs(mach_task_self(), m_port, to_underlying(m_right), -1); + break; + } + VERIFY(res == KERN_SUCCESS); +} + +ErrorOr MachPort::create_with_right(PortRight right) +{ + mach_port_t port = MACH_PORT_NULL; + auto const ret = mach_port_allocate(mach_task_self(), to_underlying(right), &port); + if (ret != KERN_SUCCESS) { + dbgln("Unable to allocate port with right: {}", to_underlying(right)); + return mach_error_to_error(ret); + } + return MachPort(right, port); +} + +MachPort MachPort::adopt_right(mach_port_t port, PortRight right) +{ + return MachPort(right, port); +} + +mach_port_t MachPort::release() +{ + return exchange(m_port, MACH_PORT_NULL); +} + +ErrorOr MachPort::insert_right(MessageRight right) +{ + auto const ret = mach_port_insert_right(mach_task_self(), m_port, m_port, to_underlying(right)); + if (ret != KERN_SUCCESS) { + dbgln("Unable to insert message right: {}", to_underlying(right)); + return mach_error_to_error(ret); + } + return MachPort(associated_port_right(right), m_port); +} + +#if defined(AK_OS_MACOS) + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +// bootstrap_register has been deprecated since macOS 10.5, but rules are more 'guidelines' than actual rules + +ErrorOr MachPort::register_with_bootstrap_server(ByteString const& service_name) +{ + if (service_name.length() > sizeof(name_t) - 1) + return Error::from_errno(E2BIG); + + auto const ret = bootstrap_register(bootstrap_port, const_cast(service_name.characters()), m_port); + if (ret != KERN_SUCCESS) { + dbgln("Unable to register {} with bootstrap on port {:p}", service_name, m_port); + return bootstrap_error_to_error(ret); + } + return {}; +} + +# pragma GCC diagnostic pop + +ErrorOr MachPort::look_up_from_bootstrap_server(ByteString const& service_name) +{ + if (service_name.length() > sizeof(name_t) - 1) + return Error::from_errno(E2BIG); + + mach_port_t port = MACH_PORT_NULL; + auto const ret = bootstrap_look_up(bootstrap_port, service_name.characters(), &port); + if (ret != KERN_SUCCESS) { + dbgln("Unable to look up service {} in bootstrap", service_name); + return bootstrap_error_to_error(ret); + } + return MachPort(PortRight::Send, port); +} + +#endif + +} diff --git a/Userland/Libraries/LibCore/MachPort.h b/Userland/Libraries/LibCore/MachPort.h new file mode 100644 index 0000000000..e4209fe4d0 --- /dev/null +++ b/Userland/Libraries/LibCore/MachPort.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#ifndef AK_OS_MACH +# error "MachPort is only available on Mach platforms" +#endif + +#include +#include +#include + +namespace Core { + +// https://www.gnu.org/software/hurd/gnumach-doc/Major-Concepts.html#Major-Concepts +class MachPort { + AK_MAKE_NONCOPYABLE(MachPort); + +public: + // https://www.gnu.org/software/hurd/gnumach-doc/Exchanging-Port-Rights.html#Exchanging-Port-Rights + enum class PortRight : mach_port_right_t { + Send = MACH_PORT_RIGHT_SEND, + Receive = MACH_PORT_RIGHT_RECEIVE, + SendOnce = MACH_PORT_RIGHT_SEND_ONCE, + PortSet = MACH_PORT_RIGHT_PORT_SET, + DeadName = MACH_PORT_RIGHT_DEAD_NAME, + }; + + enum class MessageRight : mach_msg_type_name_t { + MoveReceive = MACH_MSG_TYPE_MOVE_RECEIVE, + MoveSend = MACH_MSG_TYPE_MOVE_SEND, + MoveSendOnce = MACH_MSG_TYPE_MOVE_SEND_ONCE, + CopySend = MACH_MSG_TYPE_COPY_SEND, + MakeSend = MACH_MSG_TYPE_MAKE_SEND, + MakeSendOnce = MACH_MSG_TYPE_MAKE_SEND_ONCE, +#if defined(AK_OS_MACOS) + CopyReceive = MACH_MSG_TYPE_COPY_RECEIVE, + DisposeReceive = MACH_MSG_TYPE_DISPOSE_RECEIVE, + DisposeSend = MACH_MSG_TYPE_DISPOSE_SEND, + DisposeSendOnce = MACH_MSG_TYPE_DISPOSE_SEND_ONCE, +#endif + }; + + MachPort() = default; + MachPort(MachPort&& other); + MachPort& operator=(MachPort&& other); + ~MachPort(); + + mach_port_t release(); + + static ErrorOr create_with_right(PortRight); + static MachPort adopt_right(mach_port_t, PortRight); + + ErrorOr insert_right(MessageRight); + +#if defined(AK_OS_MACOS) + // https://opensource.apple.com/source/launchd/launchd-842.92.1/liblaunch/bootstrap.h.auto.html + static ErrorOr look_up_from_bootstrap_server(ByteString const& service_name); + ErrorOr register_with_bootstrap_server(ByteString const& service_name); +#endif + + // FIXME: mach_msg wrapper? For now just let the owner poke into the internals + mach_port_t port() { return m_port; } + +private: + MachPort(PortRight, mach_port_t); + + void unref_port(); + + PortRight m_right { PortRight::DeadName }; + mach_port_t m_port { MACH_PORT_NULL }; +}; + +Error mach_error_to_error(kern_return_t error); + +}