From e7fb58595f6b44efb608c70ce5749d5135591f12 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Wed, 8 Jul 2026 21:41:59 +0800 Subject: [PATCH] macOS support --- CMakeLists.txt | 20 ++++- compat/sbuf.h | 109 ++++++++++++++++++++++++ transports/mach.c | 18 ++-- transports/unix.c | 146 +++++++++++++++++++++++++------- xpc/base.h | 50 ++++++++++- xpc/connection.h | 3 - xpc/xpc.h | 39 ++++++--- xpc_array.c | 6 +- xpc_connection.c | 207 +++++++++++++++++++++++++++++++++++++--------- xpc_dictionary.c | 25 +++--- xpc_internal.h | 14 ++++ xpc_misc.c | 80 +++++++++++++----- xpc_type.c | 59 +++++++------ 13 files changed, 622 insertions(+), 154 deletions(-) create mode 100644 compat/sbuf.h diff --git a/CMakeLists.txt b/CMakeLists.txt index faa256d..0a3b059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,22 @@ endif() include_directories(/usr/local/include) set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fblocks -Wall -Wextra") + +# Apple Blocks are optional. On toolchains that support them (e.g. clang), the +# block-based applier/handler APIs are enabled; on toolchains without Blocks +# (e.g. GCC for powerpc-apple-darwin / macOS 10.6) the library falls back to C +# function pointers. Enable -fblocks only when the compiler accepts it. +include(CheckCCompilerFlag) +check_c_compiler_flag("-fblocks" HAVE_FBLOCKS) +if(HAVE_FBLOCKS) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fblocks") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") add_library(xpc SHARED ${SOURCES}) -add_subdirectory(examples) + +# The bundled examples use Apple Blocks in their event handlers, so they can +# only be built on a Blocks-capable toolchain. +if(HAVE_FBLOCKS) + add_subdirectory(examples) +endif() diff --git a/compat/sbuf.h b/compat/sbuf.h new file mode 100644 index 0000000..aeaf97d --- /dev/null +++ b/compat/sbuf.h @@ -0,0 +1,109 @@ +/* + * Minimal drop-in replacement for FreeBSD's safe string buffer + * API, for platforms that do not ship one (e.g. macOS). Only the subset used by + * libxpc is implemented: sbuf_new_auto(), sbuf_printf(), sbuf_finish(), + * sbuf_data() and sbuf_delete(). + * + * The buffer grows automatically and always keeps its contents NUL-terminated. + */ + +#ifndef _LIBXPC_COMPAT_SBUF_H +#define _LIBXPC_COMPAT_SBUF_H + +#include +#include +#include +#include + +struct sbuf { + char * s_buf; + size_t s_size; /* allocated capacity, including room for NUL */ + size_t s_len; /* current length, excluding NUL */ +}; + +static __inline struct sbuf * +sbuf_new_auto(void) +{ + struct sbuf *s; + + s = (struct sbuf *)malloc(sizeof(*s)); + if (s == NULL) + return (NULL); + + s->s_size = 128; + s->s_buf = (char *)malloc(s->s_size); + if (s->s_buf == NULL) { + free(s); + return (NULL); + } + + s->s_buf[0] = '\0'; + s->s_len = 0; + return (s); +} + +static __inline int +sbuf_printf(struct sbuf *s, const char *fmt, ...) +{ + va_list ap; + int needed; + size_t avail; + + if (s == NULL) + return (-1); + + for (;;) { + avail = s->s_size - s->s_len; + va_start(ap, fmt); + needed = vsnprintf(s->s_buf + s->s_len, avail, fmt, ap); + va_end(ap); + + if (needed < 0) + return (-1); + + if ((size_t)needed < avail) { + s->s_len += (size_t)needed; + return (0); + } + + /* Grow to fit and retry. */ + while (s->s_size - s->s_len <= (size_t)needed) + s->s_size *= 2; + + { + char *nbuf = (char *)realloc(s->s_buf, s->s_size); + if (nbuf == NULL) + return (-1); + s->s_buf = nbuf; + } + } +} + +static __inline int +sbuf_finish(struct sbuf *s) +{ + + if (s == NULL) + return (-1); + s->s_buf[s->s_len] = '\0'; + return (0); +} + +static __inline char * +sbuf_data(struct sbuf *s) +{ + + return (s != NULL ? s->s_buf : NULL); +} + +static __inline void +sbuf_delete(struct sbuf *s) +{ + + if (s == NULL) + return; + free(s->s_buf); + free(s); +} + +#endif /* _LIBXPC_COMPAT_SBUF_H */ diff --git a/transports/mach.c b/transports/mach.c index 2203327..1c3fa67 100644 --- a/transports/mach.c +++ b/transports/mach.c @@ -34,8 +34,10 @@ #include #include #include -#include +#include +#include +#include "xpc/xpc.h" #include "../xpc_internal.h" #define MAX_RECV 8192 @@ -110,6 +112,13 @@ mach_port_compare(xpc_port_t p1, xpc_port_t p2) return (mach_port_t)p1 == (mach_port_t)p2; } +static void +mach_client_cancel_handler(void *context) +{ + + xpc_connection_destroy_peer(context); +} + static dispatch_source_t mach_create_client_source(xpc_port_t port, void *context, dispatch_queue_t tq) { @@ -121,9 +130,7 @@ mach_create_client_source(xpc_port_t port, void *context, dispatch_queue_t tq) dispatch_set_context(ret, context); dispatch_source_set_event_handler_f(ret, xpc_connection_recv_message); - dispatch_source_set_cancel_handler(ret, ^{ - xpc_connection_destroy_peer(dispatch_get_context(ret)); - }); + dispatch_source_set_cancel_handler_f(ret, mach_client_cancel_handler); return (ret); } @@ -132,7 +139,6 @@ static dispatch_source_t mach_create_server_source(xpc_port_t port, void *context, dispatch_queue_t tq) { mach_port_t mp = (mach_port_t)port; - void *client_ctx; dispatch_source_t ret; ret = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, @@ -162,7 +168,7 @@ mach_send(xpc_port_t local, xpc_port_t remote, void *buf, size_t len, debugf("local=%d remote=%d", src, dst); - msg_size = _ALIGN(len + sizeof(struct xpc_message)); + msg_size = ALIGN(len + sizeof(struct xpc_message)); message->header.msgh_size = msg_size; message->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND); diff --git a/transports/unix.c b/transports/unix.c index 2316602..439ec5c 100644 --- a/transports/unix.c +++ b/transports/unix.c @@ -147,6 +147,47 @@ unix_port_compare(xpc_port_t p1, xpc_port_t p2) return (int)p1 == (int)p2; } +/* + * State captured for a server (listening) source. Blocks are not available in + * this build, so the values a block would have closed over are carried in a + * heap-allocated struct that becomes the source's dispatch context and is read + * back by the function-pointer handlers via dispatch_get_context(). + */ +struct unix_server_ctx { + int fd; + void * context; + dispatch_queue_t tq; +}; + +static void +unix_client_cancel_handler(void *context) +{ + struct xpc_connection *conn = context; + int fd; + + if (conn == NULL) + return; + + fd = (int)conn->xc_local_port; + shutdown(fd, SHUT_RDWR); + close(fd); + xpc_connection_destroy_peer(conn); +} + +static void +unix_server_event_handler(void *context) +{ + struct unix_server_ctx *sc = context; + int sock; + xpc_port_t client_port; + dispatch_source_t client_source; + + sock = accept(sc->fd, NULL, NULL); + client_port = (xpc_port_t)(long)sock; + client_source = unix_create_client_source(client_port, NULL, sc->tq); + xpc_connection_new_peer(sc->context, client_port, -1, client_source); +} + static dispatch_source_t unix_create_client_source(xpc_port_t port, void *context, dispatch_queue_t tq) { @@ -158,11 +199,7 @@ unix_create_client_source(xpc_port_t port, void *context, dispatch_queue_t tq) dispatch_set_context(ret, context); dispatch_source_set_event_handler_f(ret, xpc_connection_recv_message); - dispatch_source_set_cancel_handler(ret, ^{ - shutdown(fd, SHUT_RDWR); - close(fd); - xpc_connection_destroy_peer(dispatch_get_context(ret)); - }); + dispatch_source_set_cancel_handler_f(ret, unix_client_cancel_handler); return (ret); } @@ -171,21 +208,19 @@ static dispatch_source_t unix_create_server_source(xpc_port_t port, void *context, dispatch_queue_t tq) { int fd = (int)port; - void *client_ctx; + struct unix_server_ctx *sc; dispatch_source_t ret; ret = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)fd, 0, tq); - dispatch_source_set_event_handler(ret, ^{ - int sock; - xpc_port_t client_port; - dispatch_source_t client_source; - sock = accept(fd, NULL, NULL); - client_port = (xpc_port_t)(long)sock; - client_source = unix_create_client_source(client_port, NULL, tq); - xpc_connection_new_peer(context, client_port, -1, client_source); - }); + sc = malloc(sizeof(*sc)); + sc->fd = fd; + sc->context = context; + sc->tq = tq; + + dispatch_set_context(ret, sc); + dispatch_source_set_event_handler_f(ret, unix_server_event_handler); return (ret); } @@ -207,6 +242,22 @@ unix_send(xpc_port_t local, xpc_port_t remote __unused, void *buf, size_t len, memset(&msg, 0, sizeof(struct msghdr)); msg.msg_iov = &iov; msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + cmsg = NULL; + + for (i = 0; i < (int)nres; i++) { + if (res[i].xr_type == XPC_RESOURCE_FD) + nfds++; + } + +#if !defined(__APPLE__) + /* + * On FreeBSD the sender attaches an SCM_CREDS control block that the + * kernel fills in with its credentials. macOS has no equivalent; there + * the receiver reads peer credentials with getpeereid()/LOCAL_PEERPID, + * so nothing is attached to the message on the send side. + */ msg.msg_controllen = CMSG_SPACE(sizeof(struct cmsgcred)); msg.msg_control = malloc(msg.msg_controllen); @@ -214,33 +265,32 @@ unix_send(xpc_port_t local, xpc_port_t remote __unused, void *buf, size_t len, cmsg->cmsg_type = SCM_CREDS; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred)); +#endif - for (i = 0; i < nres; i++) { - if (res[i].xr_type == XPC_RESOURCE_FD) - nfds++; - } - - if (nres > 0) { + if (nfds > 0) { int *fds; - msg.msg_controllen = CMSG_SPACE(sizeof(struct cmsgcred)) + - CMSG_SPACE(nfds * sizeof(int)); + msg.msg_controllen += CMSG_SPACE(nfds * sizeof(int)); msg.msg_control = realloc(msg.msg_control, msg.msg_controllen); - cmsg = CMSG_NXTHDR(&msg, cmsg); + cmsg = (cmsg == NULL) ? CMSG_FIRSTHDR(&msg) + : CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_len = CMSG_LEN(nres * sizeof(int)); + cmsg->cmsg_len = CMSG_LEN(nfds * sizeof(int)); fds = (int *)CMSG_DATA(cmsg); - for (i = 0; i < nres; i++) { + for (i = 0; i < (int)nres; i++) { if (res[i].xr_type == XPC_RESOURCE_FD) *fds++ = res[i].xr_fd; } } - if (sendmsg(fd, &msg, 0) < 0) + if (sendmsg(fd, &msg, 0) < 0) { + free(msg.msg_control); return (-1); + } + free(msg.msg_control); return (0); } @@ -251,11 +301,13 @@ unix_recv(xpc_port_t local, xpc_port_t *remote, void *buf, size_t len, int fd = (int)local; struct msghdr msg; struct cmsghdr *cmsg; - struct cmsgcred *recv_creds = NULL; struct iovec iov = { .iov_base = buf, .iov_len = len }; int *recv_fds = NULL; size_t recv_fds_count = 0; ssize_t recvd; +#if !defined(__APPLE__) + struct cmsgcred *recv_creds = NULL; +#endif msg.msg_name = NULL; msg.msg_namelen = 0; @@ -273,17 +325,47 @@ unix_recv(xpc_port_t local, xpc_port_t *remote, void *buf, size_t len, for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { +#if !defined(__APPLE__) if (cmsg->cmsg_type == SCM_CREDS) { recv_creds = (struct cmsgcred *)CMSG_DATA(cmsg); continue; } +#endif if (cmsg->cmsg_type == SCM_RIGHTS) { recv_fds = (int *)CMSG_DATA(cmsg); - recv_fds_count = CMSG_SPACE(cmsg); + recv_fds_count = (cmsg->cmsg_len - CMSG_LEN(0)) / + sizeof(int); } } +#if defined(__APPLE__) + /* + * macOS does not deliver peer credentials as SCM_CREDS ancillary data. + * Query them directly from the connected socket instead. + */ + { + uid_t euid = (uid_t)-1; + gid_t egid = (gid_t)-1; + + if (getpeereid(fd, &euid, &egid) == 0) { + creds->xc_remote_euid = euid; + creds->xc_remote_guid = egid; + } +#if defined(LOCAL_PEERPID) + { + pid_t pid = 0; + socklen_t plen = sizeof(pid); + + if (getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &pid, + &plen) == 0) + creds->xc_remote_pid = pid; + } +#endif + debugf("remote pid=%d, uid=%d, gid=%d", creds->xc_remote_pid, + creds->xc_remote_euid, creds->xc_remote_guid); + } +#else if (recv_creds != NULL) { creds->xc_remote_pid = recv_creds->cmcred_pid; creds->xc_remote_euid = recv_creds->cmcred_euid; @@ -292,15 +374,17 @@ unix_recv(xpc_port_t local, xpc_port_t *remote, void *buf, size_t len, recv_creds->cmcred_uid, recv_creds->cmcred_gid); } +#endif - if (recv_fds != NULL) { - int i; + if (recv_fds != NULL && recv_fds_count > 0) { + size_t i; *res = malloc(sizeof(struct xpc_resource) * recv_fds_count); for (i = 0; i < recv_fds_count; i++) { (*res)[i].xr_type = XPC_RESOURCE_FD; (*res)[i].xr_fd = recv_fds[i]; } + *nres = recv_fds_count; } *remote = NULL; diff --git a/xpc/base.h b/xpc/base.h index 50c662e..0d241a3 100644 --- a/xpc/base.h +++ b/xpc/base.h @@ -15,6 +15,14 @@ __BEGIN_DECLS #define __has_attribute(x) 0 #endif // !defined(__has_attribute) +#if !defined(__has_extension) +#define __has_extension(x) 0 +#endif // !defined(__has_extension) + +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif // !defined(__has_feature) + #if __has_include() #include #else // __has_include() @@ -24,6 +32,21 @@ __BEGIN_DECLS #define __XPC_IOS_SIMULATOR_AVAILABLE_STARTING(version) #endif // __has_include() +/* + * This is a reimplementation of libxpc, not Apple's. On real macOS the SDK's + * defines __OSX_AVAILABLE_STARTING() to version-check macros + * that expand to __attribute__((availability(...))) or, on toolchains that do + * not understand that attribute (e.g. GCC for powerpc-apple-darwin targeting + * 10.6), to stray tokens that break every declaration they annotate. We never + * want those semantics here, so force the availability annotations to no-ops. + */ +#undef __OSX_AVAILABLE_STARTING +#define __OSX_AVAILABLE_STARTING(_osx, _ios) +#undef __OSX_AVAILABLE_BUT_DEPRECATED +#define __OSX_AVAILABLE_BUT_DEPRECATED(...) +#undef __XPC_IOS_SIMULATOR_AVAILABLE_STARTING +#define __XPC_IOS_SIMULATOR_AVAILABLE_STARTING(version) + #if XPC_SERVICE_MAIN_IN_LIBXPC #define XPC_HOSTING_OLD_MAIN 1 #else // XPC_SERVICE_MAIN_IN_LIBXPC @@ -160,8 +183,31 @@ __BEGIN_DECLS #define XPC_DEPRECATED /*! @parseOnly */ #define XPC_UNAVAILABLE(m) -#endif // __GNUC__ +#endif // __GNUC__ + +/* + * dispatch_block_t is normally provided by , but the old + * libdispatch shipped with macOS 10.6 does not typedef it. Provide a fallback + * so the connection barrier APIs can be declared. + * + * When building without Blocks support (this port targets toolchains that lack + * Apple Blocks), a "block" is represented as a plain C function pointer taking + * no arguments; the barrier implementation invokes it via dispatch_*_f(). This + * keeps the public API name stable across block-capable and block-free builds. + * + * Guarded with XPC_HAVE_DISPATCH_BLOCK_T so a libdispatch that already provides + * the typedef does not clash; define that macro before including this header if + * your dispatch headers declare dispatch_block_t themselves. + */ +#if !defined(XPC_HAVE_DISPATCH_BLOCK_T) +#define XPC_HAVE_DISPATCH_BLOCK_T 1 +#if __BLOCKS__ +typedef void (^dispatch_block_t)(void); +#else // __BLOCKS__ +typedef void (*dispatch_block_t)(void); +#endif // __BLOCKS__ +#endif // !defined(XPC_HAVE_DISPATCH_BLOCK_T) __END_DECLS -#endif // __XPC_BASE_H__ +#endif // __XPC_BASE_H__ diff --git a/xpc/connection.h b/xpc/connection.h index 6e7f8ef..87fb4d1 100644 --- a/xpc/connection.h +++ b/xpc/connection.h @@ -7,9 +7,6 @@ #include #endif // __XPC_INDIRECT__ -#ifndef __BLOCKS__ -#error "XPC connections require Blocks support." -#endif // __BLOCKS__ __BEGIN_DECLS diff --git a/xpc/xpc.h b/xpc/xpc.h index cb8cbbe..dda80cc 100644 --- a/xpc/xpc.h +++ b/xpc/xpc.h @@ -3,9 +3,11 @@ #ifndef __XPC_H__ #define __XPC_H__ -#ifndef __FreeBSD__ +#if !defined(__FreeBSD__) && defined(__has_include) +#if __has_include() #include #endif +#endif #include #include @@ -22,14 +24,17 @@ __BEGIN_DECLS -#ifndef __OSX_AVAILABLE_STARTING -#define __OSX_AVAILABLE_STARTING(x, y) -#endif // __OSX_AVAILABLE_STARTING - #ifndef __XPC_INDIRECT__ #define __XPC_INDIRECT__ #endif // __XPC_INDIRECT__ +/* + * __OSX_AVAILABLE_STARTING() is deliberately not defined here. On real macOS + * (pulled in transitively by below) defines it to + * version-check machinery, and then neutralizes it to a no-op so + * that toolchains which mishandle those annotations (e.g. GCC targeting an old + * powerpc-apple-darwin SDK) can still parse these declarations. + */ #include #define XPC_API_VERSION 20121012 @@ -95,6 +100,8 @@ typedef void * xpc_object_t; */ #if __BLOCKS__ typedef void (^xpc_handler_t)(xpc_object_t object); +#else +typedef void (*xpc_handler_t)(xpc_object_t object); #endif // __BLOCKS__ /*! @@ -324,8 +331,14 @@ const char *const _xpc_event_key_name; #ifndef __XPC_BUILDING_XPC__ #include #include -#if __BLOCKS__ +/* + * The connection API does not require Blocks: xpc_handler_t degrades to a C + * function pointer and the send-barrier uses dispatch_block_t (also a function + * pointer without Blocks). Only the activity API depends on Blocks, so it stays + * gated. This lets the library build on toolchains without Apple Blocks. + */ #include +#if __BLOCKS__ #include #endif // __BLOCKS__ #undef __XPC_INDIRECT__ @@ -1116,7 +1129,10 @@ xpc_shmem_map(xpc_object_t xshmem, void **region); */ #ifdef __BLOCKS__ typedef bool (^xpc_array_applier_t)(size_t index, xpc_object_t value); -#endif // __BLOCKS__ +#else // __BLOCKS__ +/* Blocks are unavailable in this build; fall back to a C function pointer. */ +typedef bool (*xpc_array_applier_t)(size_t index, xpc_object_t value); +#endif // __BLOCKS__ /*! * @function xpc_array_create @@ -1253,12 +1269,10 @@ xpc_array_get_value(xpc_object_t xarray, size_t index); * You should not modify an array's contents during iteration. The array indexes * are iterated in order. */ -#ifdef __BLOCKS__ __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) XPC_EXPORT XPC_NONNULL_ALL bool xpc_array_apply(xpc_object_t xarray, xpc_array_applier_t applier); -#endif // __BLOCKS__ #pragma mark Array Primitive Setters /*! @@ -1782,7 +1796,10 @@ xpc_array_create_connection(xpc_object_t xarray, size_t index); */ #ifdef __BLOCKS__ typedef bool (^xpc_dictionary_applier_t)(const char *key, xpc_object_t value); -#endif // __BLOCKS__ +#else // __BLOCKS__ +/* Blocks are unavailable in this build; fall back to a C function pointer. */ +typedef bool (*xpc_dictionary_applier_t)(const char *key, xpc_object_t value); +#endif // __BLOCKS__ /*! * @function xpc_dictionary_create @@ -1936,12 +1953,10 @@ xpc_dictionary_get_count(xpc_object_t xdict); * You should not modify a dictionary's contents during iteration. There is no * guaranteed order of iteration over dictionaries. */ -#ifdef __BLOCKS__ __OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0) XPC_EXPORT XPC_NONNULL_ALL bool xpc_dictionary_apply(xpc_object_t xdict, xpc_dictionary_applier_t applier); -#endif // __BLOCKS__ /*! * @function xpc_dictionary_get_remote_connection diff --git a/xpc_array.c b/xpc_array.c index 38b6edd..4c60ade 100644 --- a/xpc_array.c +++ b/xpc_array.c @@ -55,8 +55,10 @@ xpc_array_set_value(xpc_object_t xarray, size_t index, xpc_object_t value) arr = &xo->xo_array; i = 0; - if (index == XPC_ARRAY_APPEND) - return xpc_array_append_value(xarray, value); + if (index == XPC_ARRAY_APPEND) { + xpc_array_append_value(xarray, value); + return; + } if (index >= (size_t)xo->xo_size) return; diff --git a/xpc_connection.c b/xpc_connection.c index a55bf7a..9bd35ab 100644 --- a/xpc_connection.c +++ b/xpc_connection.c @@ -26,15 +26,65 @@ */ #include -#include -#include -#include +#include + +#ifdef __APPLE__ +#include +#endif + +#include "xpc/xpc.h" #include "xpc_internal.h" -#define XPC_CONNECTION_NEXT_ID(conn) (atomic_fetchadd_long(&conn->xc_last_id, 1)) +#define XPC_CONNECTION_NEXT_ID(conn) atomic_fetch_add(&conn->xc_last_id, 1) static void xpc_send(xpc_connection_t xconn, xpc_object_t message, uint64_t id); +struct xpc_send_context { + xpc_connection_t xconn; + xpc_object_t message; + uint64_t id; +}; + +struct xpc_handler_context { + xpc_connection_t xconn; + xpc_handler_t handler; +}; + +struct xpc_reply_context { + xpc_object_t result; + dispatch_semaphore_t sem; +}; + +struct xpc_new_peer_context { + struct xpc_connection *conn; + struct xpc_connection *peer; + xpc_object_t result; + uint64_t id; +}; + +struct xpc_destroy_peer_context { + struct xpc_connection *conn; + struct xpc_connection *parent; +}; + +struct xpc_callback_context { + struct xpc_connection *conn; + struct xpc_pending_call *call; + xpc_object_t result; +}; + +struct xpc_dispatch_context { + struct xpc_connection *conn; + xpc_object_t result; +}; + +static void xpc_send_message_f(void *context); +static void xpc_new_peer_handler_f(void *context); +static void xpc_mach_new_peer_handler_f(void *context); +static void xpc_destroy_peer_handler_f(void *context); +static void xpc_callback_handler_f(void *context); +static void xpc_dispatch_handler_f(void *context); + xpc_connection_t xpc_connection_create(const char *name, dispatch_queue_t targetq) { @@ -130,7 +180,7 @@ xpc_connection_set_event_handler(xpc_connection_t xconn, debugf("connection=%p", xconn); conn = (struct xpc_connection *)xconn; - conn->xc_handler = (xpc_handler_t)Block_copy(handler); + conn->xc_handler = handler; } void @@ -167,11 +217,20 @@ xpc_connection_resume(xpc_connection_t xconn) dispatch_resume(conn->xc_recv_queue); } +static void +xpc_send_message_f(void *context) +{ + struct xpc_send_context *ctx = context; + xpc_send(ctx->xconn, ctx->message, ctx->id); + free(ctx); +} + void xpc_connection_send_message(xpc_connection_t xconn, xpc_object_t message) { struct xpc_connection *conn; + struct xpc_send_context *ctx; uint64_t id; conn = (struct xpc_connection *)xconn; @@ -180,9 +239,11 @@ xpc_connection_send_message(xpc_connection_t xconn, if (id == 0) id = XPC_CONNECTION_NEXT_ID(conn); - dispatch_async(conn->xc_send_queue, ^{ - xpc_send(xconn, message, id); - }); + ctx = malloc(sizeof(*ctx)); + ctx->xconn = xconn; + ctx->message = message; + ctx->id = id; + dispatch_async_f(conn->xc_send_queue, ctx, xpc_send_message_f); } void @@ -191,6 +252,7 @@ xpc_connection_send_message_with_reply(xpc_connection_t xconn, { struct xpc_connection *conn; struct xpc_pending_call *call; + struct xpc_send_context *ctx; conn = (struct xpc_connection *)xconn; call = malloc(sizeof(struct xpc_pending_call)); @@ -199,27 +261,40 @@ xpc_connection_send_message_with_reply(xpc_connection_t xconn, call->xp_queue = targetq; TAILQ_INSERT_TAIL(&conn->xc_pending, call, xp_link); - dispatch_async(conn->xc_send_queue, ^{ - xpc_send(xconn, message, call->xp_id); - }); + ctx = malloc(sizeof(*ctx)); + ctx->xconn = xconn; + ctx->message = message; + ctx->id = call->xp_id; + dispatch_async_f(conn->xc_send_queue, ctx, xpc_send_message_f); +} + +static struct xpc_reply_context *sync_reply_context = NULL; +static void +xpc_sync_reply_handler(xpc_object_t o) +{ + if (sync_reply_context) { + sync_reply_context->result = o; + dispatch_semaphore_signal(sync_reply_context->sem); + } } xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t conn, xpc_object_t message) { - __block xpc_object_t result; - dispatch_semaphore_t sem = dispatch_semaphore_create(0); + struct xpc_reply_context ctx; + ctx.result = NULL; + ctx.sem = dispatch_semaphore_create(0); + sync_reply_context = &ctx; xpc_connection_send_message_with_reply(conn, message, NULL, - ^(xpc_object_t o) { - result = o; - dispatch_semaphore_signal(sem); - }); + xpc_sync_reply_handler); - dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); - return (result); + dispatch_semaphore_wait(ctx.sem, DISPATCH_TIME_FOREVER); + dispatch_release(ctx.sem); + sync_reply_context = NULL; + return (ctx.result); } void @@ -228,7 +303,8 @@ xpc_connection_send_barrier(xpc_connection_t xconn, dispatch_block_t barrier) struct xpc_connection *conn; conn = (struct xpc_connection *)xconn; - dispatch_sync(conn->xc_send_queue, barrier); + dispatch_sync_f(conn->xc_send_queue, (void *)barrier, + (dispatch_function_t)barrier); } void @@ -401,28 +477,48 @@ xpc_connection_new_peer(void *context, xpc_port_t local, xpc_port_t remote, disp TAILQ_INSERT_TAIL(&conn->xc_peers, peer, xc_link); if (src) { + struct xpc_new_peer_context *ctx; dispatch_set_context(src, peer); dispatch_resume(src); - dispatch_async(conn->xc_target_queue, ^{ - conn->xc_handler(peer); - }); + ctx = malloc(sizeof(*ctx)); + ctx->conn = conn; + ctx->peer = peer; + dispatch_async_f(conn->xc_target_queue, ctx, xpc_new_peer_handler_f); } return (peer); } +static void +xpc_new_peer_handler_f(void *context) +{ + struct xpc_new_peer_context *ctx = context; + ctx->conn->xc_handler((xpc_connection_t)ctx->peer); + free(ctx); +} + +static void +xpc_destroy_peer_handler_f(void *context) +{ + struct xpc_destroy_peer_context *ctx = context; + ctx->conn->xc_handler((xpc_object_t)XPC_ERROR_CONNECTION_INVALID); + free(ctx); +} + void xpc_connection_destroy_peer(void *context) { struct xpc_connection *conn, *parent; + struct xpc_destroy_peer_context *ctx; conn = context; parent = conn->xc_parent; if (conn->xc_parent != NULL) { - dispatch_async(parent->xc_target_queue, ^{ - conn->xc_handler((xpc_object_t)XPC_ERROR_CONNECTION_INVALID); - }); + ctx = malloc(sizeof(*ctx)); + ctx->conn = conn; + ctx->parent = parent; + dispatch_async_f(parent->xc_target_queue, ctx, xpc_destroy_peer_handler_f); TAILQ_REMOVE(&parent->xc_peers, conn, xc_link); } @@ -430,6 +526,25 @@ xpc_connection_destroy_peer(void *context) dispatch_release(conn->xc_recv_source); } +static void +xpc_callback_handler_f(void *context) +{ + struct xpc_callback_context *ctx = context; + ctx->call->xp_handler(ctx->result); + TAILQ_REMOVE(&ctx->conn->xc_pending, ctx->call, xp_link); + free(ctx->call); + free(ctx); +} + +static void +xpc_dispatch_handler_f(void *context) +{ + struct xpc_dispatch_context *ctx = context; + debugf("calling handler=%p", ctx->conn->xc_handler); + ctx->conn->xc_handler(ctx->result); + free(ctx); +} + static void xpc_connection_dispatch_callback(struct xpc_connection *conn, xpc_object_t result, uint64_t id) @@ -438,22 +553,22 @@ xpc_connection_dispatch_callback(struct xpc_connection *conn, TAILQ_FOREACH(call, &conn->xc_pending, xp_link) { if (call->xp_id == id) { - dispatch_async(conn->xc_target_queue, ^{ - call->xp_handler(result); - TAILQ_REMOVE(&conn->xc_pending, call, - xp_link); - free(call); - }); + struct xpc_callback_context *ctx = malloc(sizeof(*ctx)); + ctx->conn = conn; + ctx->call = call; + ctx->result = result; + dispatch_async_f(conn->xc_target_queue, ctx, xpc_callback_handler_f); return; } } if (conn->xc_handler) { + struct xpc_dispatch_context *ctx; debugf("yes"); - dispatch_async(conn->xc_target_queue, ^{ - debugf("calling handler=%p", conn->xc_handler); - conn->xc_handler(result); - }); + ctx = malloc(sizeof(*ctx)); + ctx->conn = conn; + ctx->result = result; + dispatch_async_f(conn->xc_target_queue, ctx, xpc_dispatch_handler_f); } } @@ -513,14 +628,26 @@ xpc_connection_recv_mach_message(void *context) peer = xpc_connection_get_peer(context, remote); if (!peer) { + struct xpc_new_peer_context *ctx; debugf("new peer on port %s", transport->xt_port_to_string(remote)); peer = xpc_connection_new_peer(context, conn->xc_local_port, remote, NULL); - dispatch_async(conn->xc_target_queue, ^{ - conn->xc_handler(peer); - xpc_connection_dispatch_callback(peer, result, id); - }); + ctx = malloc(sizeof(*ctx)); + ctx->conn = conn; + ctx->peer = peer; + ctx->result = result; + ctx->id = id; + dispatch_async_f(conn->xc_target_queue, ctx, xpc_mach_new_peer_handler_f); } else xpc_connection_dispatch_callback(peer, result, id); +} + +static void +xpc_mach_new_peer_handler_f(void *context) +{ + struct xpc_new_peer_context *ctx = context; + ctx->conn->xc_handler((xpc_connection_t)ctx->peer); + xpc_connection_dispatch_callback(ctx->peer, ctx->result, ctx->id); + free(ctx); } \ No newline at end of file diff --git a/xpc_dictionary.c b/xpc_dictionary.c index fa8e570..a59bc57 100644 --- a/xpc_dictionary.c +++ b/xpc_dictionary.c @@ -26,6 +26,7 @@ */ #include + #include "xpc/xpc.h" #include "xpc_internal.h" #include "mpack.h" @@ -115,24 +116,26 @@ xpc2mpack(mpack_writer_t *writer, xpc_object_t obj) struct xpc_object *xotmp = obj; switch (xotmp->xo_xpc_type) { - case _XPC_TYPE_DICTIONARY: + case _XPC_TYPE_DICTIONARY: { + struct xpc_dict_pair *pair; mpack_start_map(writer, xpc_dictionary_get_count(obj)); - xpc_dictionary_apply(obj, ^(const char *k, xpc_object_t v) { - mpack_write_cstr(writer, k); - xpc2mpack(writer, v); - return ((bool)true); - }); + TAILQ_FOREACH(pair, &xotmp->xo_dict, xo_link) { + mpack_write_cstr(writer, pair->key); + xpc2mpack(writer, pair->value); + } mpack_finish_map(writer); break; + } - case _XPC_TYPE_ARRAY: + case _XPC_TYPE_ARRAY: { + struct xpc_object *item; mpack_start_array(writer, xpc_array_get_count(obj)); - xpc_array_apply(obj, ^(size_t index __unused, xpc_object_t v) { - xpc2mpack(writer, v); - return ((bool)true); - }); + TAILQ_FOREACH(item, &xotmp->xo_array, xo_link) { + xpc2mpack(writer, item); + } mpack_finish_map(writer); break; + } case _XPC_TYPE_NULL: mpack_write_nil(writer); diff --git a/xpc_internal.h b/xpc_internal.h index c0828d3..fcc782a 100644 --- a/xpc_internal.h +++ b/xpc_internal.h @@ -33,6 +33,20 @@ #include #include "mpack.h" +/* + * __private_extern__ is an Apple/clang keyword used to give a symbol hidden + * visibility. GCC (including the powerpc-apple-darwin toolchain used for macOS + * 10.6) does not recognize it as a keyword, so fall back to the visibility + * attribute, or nothing on compilers that lack it. + */ +#ifndef __private_extern__ +#if defined(__GNUC__) +#define __private_extern__ __attribute__((visibility("hidden"))) +#else +#define __private_extern__ extern +#endif +#endif + #ifdef XPC_DEBUG #define debugf(...) \ do { \ diff --git a/xpc_misc.c b/xpc_misc.c index 4ebc354..03d572e 100644 --- a/xpc_misc.c +++ b/xpc_misc.c @@ -26,22 +26,43 @@ */ #include +#include #include +#if defined(__FreeBSD__) #include -#include +#include +#elif defined(__APPLE__) +#include "compat/sbuf.h" /* macOS has sys/sbuf.h, but it lacks needed stuff. */ +#elif defined(__has_include) +#if __has_include() +#include +#else +#include "compat/sbuf.h" +#endif +#else +#include "compat/sbuf.h" +#endif #include -#include #include +#include +#include + #include "xpc/xpc.h" #include "xpc_internal.h" +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif + #define RECV_BUFFER_SIZE 65536 static void xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf, int level); extern struct xpc_transport unix_transport __attribute__((weak)); +#ifdef MACH extern struct xpc_transport mach_transport __attribute__((weak)); +#endif static struct xpc_transport *selected_transport = NULL; struct xpc_transport * @@ -54,7 +75,11 @@ xpc_get_transport() selected_transport = &unix_transport; if (!strcmp(env, "mach")) +#ifdef MACH selected_transport = &mach_transport; +#else + selected_transport = &unix_transport; +#endif } else { #ifdef MACH selected_transport = &mach_transport; @@ -160,7 +185,7 @@ xpc_retain(xpc_object_t obj) struct xpc_object *xo; xo = obj; - atomic_add_int(&xo->xo_refcnt, 1); + __atomic_add_fetch(&xo->xo_refcnt, 1, __ATOMIC_RELAXED); return (obj); } @@ -170,7 +195,7 @@ xpc_release(xpc_object_t obj) struct xpc_object *xo; xo = obj; - if (atomic_fetchadd_int(&xo->xo_refcnt, -1) > 1) + if (__atomic_fetch_sub(&xo->xo_refcnt, 1, __ATOMIC_ACQ_REL) > 1) return; xpc_object_destroy(xo); @@ -213,9 +238,6 @@ static void xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf, int level) { struct xpc_object *xo = obj; - struct uuid *id; - char *uuid_str; - uint32_t uuid_status; if (obj == NULL) { sbuf_printf(sbuf, "\n"); @@ -225,23 +247,27 @@ xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf, int level) sbuf_printf(sbuf, "(%s) ", _xpc_get_type_name(obj)); switch (xo->xo_xpc_type) { - case _XPC_TYPE_DICTIONARY: + case _XPC_TYPE_DICTIONARY: { + struct xpc_dict_pair *pair; sbuf_printf(sbuf, "\n"); - xpc_dictionary_apply(xo, ^(const char *k, xpc_object_t v) { - sbuf_printf(sbuf, "%*s\"%s\": ", level * 4, " ", k); - xpc_copy_description_level(v, sbuf, level + 1); - return ((bool)true); - }); + TAILQ_FOREACH(pair, &xo->xo_dict, xo_link) { + sbuf_printf(sbuf, "%*s\"%s\": ", level * 4, " ", pair->key); + xpc_copy_description_level(pair->value, sbuf, level + 1); + } break; + } - case _XPC_TYPE_ARRAY: + case _XPC_TYPE_ARRAY: { + struct xpc_object *item; + size_t idx = 0; sbuf_printf(sbuf, "\n"); - xpc_array_apply(xo, ^(size_t idx, xpc_object_t v) { + TAILQ_FOREACH(item, &xo->xo_array, xo_link) { sbuf_printf(sbuf, "%*s%ld: ", level * 4, " ", idx); - xpc_copy_description_level(v, sbuf, level + 1); - return ((bool)true); - }); + xpc_copy_description_level(item, sbuf, level + 1); + idx++; + } break; + } case _XPC_TYPE_BOOL: sbuf_printf(sbuf, "%s\n", @@ -268,12 +294,22 @@ xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf, int level) xpc_date_get_value(obj)); break; - case _XPC_TYPE_UUID: - id = (struct uuid *)xpc_uuid_get_bytes(obj); - uuid_to_string(id, &uuid_str, &uuid_status); - sbuf_printf(sbuf, "%s\n", uuid_str); + case _XPC_TYPE_UUID: { + const uint8_t *id = xpc_uuid_get_bytes(obj); +#if defined(__FreeBSD__) + char *uuid_str = NULL; + uint32_t uuid_status; + uuid_to_string((const struct uuid *)id, &uuid_str, &uuid_status); + sbuf_printf(sbuf, "%s\n", uuid_str != NULL ? uuid_str : ""); free(uuid_str); +#else + /* uuid_unparse() from , available on macOS/libc. */ + char uuid_str[37]; + uuid_unparse(*(const uuid_t *)id, uuid_str); + sbuf_printf(sbuf, "%s\n", uuid_str); +#endif break; + } case _XPC_TYPE_ENDPOINT: sbuf_printf(sbuf, "<%ld>\n", xo->xo_int); diff --git a/xpc_type.c b/xpc_type.c index 3f727a0..f256f1d 100644 --- a/xpc_type.c +++ b/xpc_type.c @@ -26,10 +26,15 @@ */ #include +#include #include #include "xpc/xpc.h" #include "xpc_internal.h" +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif + struct _xpc_type_s { }; @@ -289,7 +294,7 @@ xpc_data_create(const void *bytes, size_t length) return _xpc_prim_create(_XPC_TYPE_DATA, val, length); } -#ifdef MACH +#if 0 xpc_object_t xpc_data_create_with_dispatch_data(dispatch_data_t ddata) { @@ -460,21 +465,26 @@ xpc_copy(xpc_object_t obj) return (xpc_data_create(newdata, xpc_data_get_length(obj))); - case _XPC_TYPE_DICTIONARY: + case _XPC_TYPE_DICTIONARY: { + struct xpc_dict_pair *pair; + struct xpc_object *xo = obj; xotmp = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_apply(obj, ^(const char *k, xpc_object_t v) { - xpc_dictionary_set_value(xotmp, k, xpc_copy(v)); - return (bool)true; - }); + TAILQ_FOREACH(pair, &xo->xo_dict, xo_link) { + xpc_dictionary_set_value(xotmp, pair->key, xpc_copy(pair->value)); + } return (xotmp); + } - case _XPC_TYPE_ARRAY: + case _XPC_TYPE_ARRAY: { + struct xpc_object *item, *xo = obj; + size_t idx = 0; xotmp = xpc_array_create(NULL, 0); - xpc_array_apply(obj, ^(size_t idx, xpc_object_t v) { - xpc_array_set_value(xotmp, idx, xpc_copy(v)); - return ((bool)true); - }); + TAILQ_FOREACH(item, &xo->xo_array, xo_link) { + xpc_array_set_value(xotmp, idx, xpc_copy(item)); + idx++; + } return (xotmp); + } } return (0); @@ -495,7 +505,7 @@ size_t xpc_hash(xpc_object_t obj) { struct xpc_object *xo; - __block size_t hash = 0; + size_t hash = 0; xo = obj; switch (xo->xo_xpc_type) { @@ -516,21 +526,24 @@ xpc_hash(xpc_object_t obj) xpc_data_get_bytes_ptr(obj), xpc_data_get_length(obj))); - case _XPC_TYPE_DICTIONARY: - xpc_dictionary_apply(obj, ^(const char *k, xpc_object_t v) { - hash ^= xpc_data_hash((const uint8_t *)k, strlen(k)); - hash ^= xpc_hash(v); - return ((bool)true); - }); + case _XPC_TYPE_DICTIONARY: { + struct xpc_dict_pair *pair; + struct xpc_object *xo = obj; + TAILQ_FOREACH(pair, &xo->xo_dict, xo_link) { + hash ^= xpc_data_hash((const uint8_t *)pair->key, strlen(pair->key)); + hash ^= xpc_hash(pair->value); + } return (hash); + } - case _XPC_TYPE_ARRAY: - xpc_array_apply(obj, ^(size_t idx, xpc_object_t v) { - hash ^= xpc_hash(v); - return ((bool)true); - }); + case _XPC_TYPE_ARRAY: { + struct xpc_object *item, *xo = obj; + TAILQ_FOREACH(item, &xo->xo_array, xo_link) { + hash ^= xpc_hash(item); + } return (hash); } + } return (0); }