freebsd-libxpc — porting Apple XPC onto our mach.ko

A porting plan for libxpc on FreeBSD, dependent on the now-working freebsd-launchd-mach Mach kmod. XPC is the userland abstraction that every Apple system daemon expects: connection-based IPC with typed dictionaries, dispatch-queue event delivery, and named launchd-bootstrap lookups. Without it, the daemons we want to port (launchd's modern paths, configd, asl, mDNSResponder) have no client surface. This plan surveys the source landscape, maps the dependencies, scopes a launchd-only MVP, and assesses how complete the related daemon ports already are.

Primary goal: drag FreeBSD into the 21st century — modern service IPC, modern daemon model, modern network config. macOS app compatibility is a downstream bonus. GNUstep stays for the application / framework layer in the gershwin desktop overlay; Apple source covers only the lower system-services layer (this ISO). The CoreFoundation engine for system services is swift-corelibs-foundation's pure-C CF, not GNUstep libs-corebase — see launchctl-corefoundation-spike for the audit.

Note on §9.5 / §9.6 / §11 below. These sections originally recommended a hybrid CoreFoundation built from GNUstep libs-corebase + a supplementary libCFRuntime ported from CF-Lite-1153.18. That recommendation is superseded. The current authoritative source for the CF question is freebsd-launchctl-corefoundation-spike, which audited the three candidate CF implementations (libs-base, libs-corebase, swift-corelibs-foundation) against actual launchctl.c usage and found that swift-corelibs-foundation's pure-C CF (built with DEPLOYMENT_RUNTIME_SWIFT=0) is the only viable choice — libs-corebase's plist parser is stubbed, which is fatal for launchctl. The remainder of this libxpc plan — what libxpc does, how it fits with launchd, the Mach integration — remains accurate; only the CF-source recommendation has changed.

Contents

  1. What libxpc is and what it does
  2. Why we need it
  3. Genealogy: Apple → iX/NextBSD → ravynOS → other forks
  4. Where the source actually comes from
  5. Public API surface
  6. Dependencies: dispatch, Mach, libnv, bootstrap
  7. MVP subset for launchd
  8. Related-daemon completeness audit
  9. GNUstep compatibility — libobjc2, corebase, Foundation
  10. Libsystem strategy — port sub-libraries, never the umbrella
  11. Risks and decisions
  12. Phased porting plan
  13. References & key paths

1. What libxpc is and what it does

XPC is Apple's high-level inter-process communication framework, layered on top of Mach IPC and libdispatch. It has two faces:

On macOS, libxpc is invoked by basically anything that talks to a system service: apps talking to configd, mDNSResponder, opendirectoryd, tccd; app extensions; helper tools; launchd activating services on-demand because client connections come in via XPC bootstrap lookups. The whole on-demand-activation story for modern daemons routes through libxpc.

Under the hood it uses Mach IPC for transport, libdispatch for event delivery, and bplist for serialization. The NextBSD / ravynOS reimplementation we inherit substitutes FreeBSD's libnv for bplist — a fundamental wire-format departure that affects nothing on the API surface but means we will never bit-exchange messages with Apple binaries.

2. Why we need it

libxpc is the glue between mach.ko (now done, Phase B Tier 1 complete) and the daemon layer we actually want to ship. Without libxpc:

Skipping libxpc means rewriting every daemon's IPC layer in something else — D-Bus, hand-rolled Mach, or AF_UNIX. Ugly either way, and it cuts us off from drop-in source ports of Apple system services.

3. Genealogy

The lineage of the libxpc implementation we'd actually fork from:

Apple public headers (xpc.h, connection.h: copyright 2009-2011 Apple Inc.)
        |
        v
iXsystems / Jakub Klama 2014-2015 skeleton C reimplementation
   (copyright "2014-2015 iXsystems, Inc." on every .c)
        |
        v
NextBSD (iXsystems-sponsored, Jordan Hubbard + Kip Macy, 2014-2016)
   * lib/libxpc/ + lib/libdispatch/ + lib/liblaunch/ + sbin/launchd/
   * github.com/NextBSD/NextBSD — dormant since early 2016
        |
        v
airyxOS (~2021-2022)
        |
        v
ravynOS (2022-present, history squashed 2025-09-28)
   * Same skeleton, with substantive object-model cleanup
   * github.com/ravynsoft/ravynos

Apple has never released libxpc as source. The last published launchd was launchd-842.92.1 in 2014; from OS X 10.10 onward, launchd's job-management logic was absorbed into the closed-source libxpc. Apple's Libsystem repo (apple-oss-distributions/Libsystem, latest tag Libsystem-1356 2025-10-04) lists xpc as a binary dependency on line 28 of requiredlibs — that is the most public mention you get. The headers /usr/include/xpc/*.h ship in the macOS SDK as declarations only; no implementation.

So when we say "port libxpc" we mean port the iXsystems / NextBSD skeleton — built against Apple's public headers but with the implementation written from scratch by a different team. We are not forking an Apple codebase. There is no opensource Apple libxpc to fork.

NextBSD vs ravynOS — how divergent are they?

Small in raw LoC, substantial in semantics. Both trees are at lib/libxpc/ with identical 7-header layout and a shared bundled libnv. diff -rq shows 9 differing files; the XPC-specific changes ravynOS made on top of NextBSD (mostly around 2022, since squashed):

NextBSD's own commit messages call its tree a "first pass" — one says verbatim "paper over disastrous state of libxpc by disabling Werror". ravynOS is closer to working code. Decision: fork from ravynOS, not NextBSD.

Third-party forks worth knowing about

4. Where the source actually comes from

Concrete answers to "what sources can we get it from?":

SourceWhat's thereVerdict
Apple opensource.apple.com Public headers only (xpc.h, connection.h, etc. via macOS SDK). No implementation, ever. Libsystem repo links xpc as binary. unusable for implementation
github.com/apple-oss-distributions/launchd Last tag launchd-842.92.1, 2014-08-13 (OS X 10.9.x). Pre-libxpc-split. No XPC source. historical reference only
ravynOS lib/libxpc/ iXsystems skeleton + 2022-era cleanup. ~2,348 LoC of XPC logic + ~4,077 LoC bundled libnv. ~78 functions implemented across 5 .c files. FreeBSD bsd.lib.mk compatible. fork from this
NextBSD lib/libxpc/ Same skeleton, dormant since 2015. ~90 LoC behind ravynOS but semantically much less polished. historical
darling-libxpc NextBSD fork extended for Darling. Useful for newer activity-key declarations and as API cross-check. reference

Did Apple ever open-source libxpc? No. Public headers, yes; implementation, never. Every "libxpc" you can find on GitHub is descended from the iXsystems/Klama 2014-2015 reimplementation.

Did iXsystems contribute the source? Yes — via NextBSD, sponsored by iX (Jordan Hubbard was CTO at the time). Every .c file in both NextBSD and ravynOS trees carries Copyright 2014-2015 iXsystems, Inc. The implementation was written from scratch against Apple's public headers. ravynOS inherited from NextBSD (via airyxOS).

5. Public API surface

Public headers under ravynos/lib/libxpc/xpc/ — 7 files, 169 XPC_EXPORT decorations total. Categorized:

HeaderLinesSurface
base.h 165 Attribute macros only (XPC_EXPORT, XPC_NONNULL*, XPC_MALLOC, XPC_INLINE). No functions. The bootstrap header.
xpc.h 2531 The umbrella header: refcount + type system (xpc_retain/release/copy/equal/hash/get_type/copy_description), XPC_TYPE_* globals, primitive constructors (null, bool, int64, uint64, double, date, data, string, uuid, fd, shmem), 24 array APIs, ~30 dictionary APIs, service-runtime (xpc_main, xpc_transaction_begin/_end). 113 exports.
connection.h 718 Connection lifecycle: xpc_connection_create, xpc_connection_create_mach_service, xpc_connection_create_from_endpoint, set_target_queue/set_event_handler/suspend/resume/send_message/send_barrier/send_message_with_reply[_sync]/cancel, peer-info (get_euid/get_egid/get_pid/get_asid), context/finalizer, error dictionaries. 24 exports.
activity.h 427 Background scheduling (xpc_activity_register, criteria dictionaries, defer/state). 29 exports, stubbed/unimplemented in this tree (no xpc_activity.c).
endpoint.h 22 One function: xpc_endpoint_create.
debug.h 23 xpc_debugger_api_misuse_info.
launchd.h 24 Project-specific surface that substitutes Apple's private xpc/private.h: xpc_strerror, xpc_copy_entitlement_for_token, xpc_pipe_routine_reply, xpc_pipe_try_receive, xpc_call_wakeup, xpc_dictionary_{set_mach_recv,set_mach_send,copy_mach_send}, xpc_dictionary_get_audit_token, xpc_copy_entitlements_for_pid, ld2xpc(launch_data_t).

Net real entry-point count: ~70. The 169 raw export count is misleading — ~100 of those are constants, error-dictionary globals, and type-token symbols. The 78 functions implemented in the .c files map to roughly the right surface for an MVP.

6. Dependencies

libdispatch — hard dependency

libxpc cannot exist without libdispatch underneath. Connection event handlers run on dispatch queues; message delivery uses dispatch_source_create with DISPATCH_SOURCE_TYPE_MACH_RECV. From the ravynOS tree, libxpc calls:

Per the gershwin-on-freebsd integration, libdispatch is already shipping via gershwin-developer at /System/Library/Libraries. Critical verification needed: the libdispatch we have must actually wire DISPATCH_SOURCE_TYPE_MACH_RECV to Mach ports, not just expose the symbol. If gershwin's libdispatch was built for a pre-Mach FreeBSD, we may need to rebuild it against our mach.ko-exposed primitives.

Mach IPC — partial dependency on our existing mach.ko

libxpc calls into Mach from xpc_connection.c and xpc_misc.c. Status against what freebsd-launchd-mach currently wires:

Mach callStatus in mach.koAction needed
mach_task_self()wired as task_self_trap via libmachnone
mach_msg_send / mach_msgwired as mach_msg_trap via libmach (6-arg)verify MACH_RCV_TRAILER_AUDIT trailer population
mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &p)not wiredadd _kernelrpc_mach_port_allocate_trap
mach_port_insert_right(task, name, port, type)not wiredadd _kernelrpc_mach_port_insert_right_trap
mach_port_deallocate(task, port)not wiredadd _kernelrpc_mach_port_deallocate_trap
task_get_special_port / task_set_special_portnot wiredtypically MIG RPC over task port, not direct trap; needs liblaunch-side stub
bootstrap_check_in / bootstrap_look_upnot wiredbiggest gap: needs a userspace bootstrap server (launchd's bootstrap subsystem) plus MIG-generated _bootstrap_* stubs plus a bootstrap_port task-special-port slot

Bottom line: we have messaging working from userland. We do not yet have port management from userland, and we do not have a name service at all. The bootstrap-server gap is the single biggest piece — it implies a launchd-Mach-helper running as PID 1 (or thereabouts) that owns the bootstrap port and answers bootstrap_look_up calls. The good news: NextBSD/ravynOS have a working bootstrap server we can inherit.

libnv — FreeBSD-native serialization

The NextBSD/ravynOS libxpc uses FreeBSD's libnv (nv-list) for wire serialization, not Apple's bplist. The two libnv files (subr_nvlist.c 2,572 lines, subr_nvpair.c 1,505 lines) are bundled inside libxpc in both trees — they dominate the line count (~62% of total). This is a design departure: XPC objects get converted to nvlist, packed via nvlist_pack_buffer, sent over Mach.

Implication: we can never bit-exchange messages with a real macOS app. That's fine for our use case — everything in the stack will be FreeBSD-native binaries. The compatibility we keep is at the source level (Apple-shape API) not the wire level.

Other

7. MVP subset for launchd

The realistic libxpc-for-launchd surface, by tier:

Tier 1 — launchd alone (~35 functions, 7 wrapped types)

Types: xpc_object_t, xpc_type_t, xpc_dictionary, xpc_array, xpc_string, xpc_int64, xpc_uint64, xpc_bool, plus the _xpc_type_* global symbols.

Lifecycle / introspection: xpc_retain, xpc_release, xpc_get_type, xpc_copy, xpc_copy_description, xpc_strerror.

Dictionary: xpc_dictionary_create, xpc_dictionary_create_reply, xpc_dictionary_get_value/set_value, get_string/set_string, get_int64/set_int64, get_uint64/set_uint64, get_bool.

Array: xpc_array_create, xpc_array_append_value, xpc_array_set_string, xpc_array_set_uint64.

Scalars: xpc_bool_get_value, xpc_uint64_get_value, xpc_int64_get_value.

Mach-tied (depends on mach.ko expansion): xpc_dictionary_set_mach_send, set_mach_recv, copy_mach_send, xpc_dictionary_get_audit_token, xpc_pipe_try_receive, xpc_pipe_routine_reply. These are the chokepoint — the entire NextBSD launchd dispatch loop in runtime.c runs on xpc_pipe_try_receive against a Mach port set.

Tier 2 — adds configd and aslmanager (~25 more functions)

The major addition is the xpc_connection client API: xpc_connection_create_mach_service, set_event_handler, resume, send_message, send_message_with_reply_sync, cancel, get_pid/euid/egid, set_target_queue, set_context/get_context, set_finalizer_f, xpc_dictionary_get_remote_connection. Plus xpc_create_from_plist, xpc_data_create/get_bytes_ptr/get_length, xpc_dictionary_set_data/get_data, set_uuid/get_uuid, set_bool, xpc_dictionary_apply, get_count, xpc_connection_copy_entitlement_value.

NextBSD's lib/libxpc/xpc_connection.c (501 lines) is the natural starting point and already speaks nvlist-over-Mach.

Tier 3 — defer or stub

Honest summary: ~35 functions for launchd-only, ~60 for launchd+configd, ~70 for everything our daemon stack actually needs. NextBSD's tree at ~7k LoC (including bundled libnv) is roughly the right size. The cost is dominated not by libxpc proper but by the prerequisites: (a) bootstrap server + bootstrap_port; (b) Mach port-right management traps in mach.ko; (c) audit-trailer support; (d) fileport shim; (e) verified libdispatch Mach-receive source.

To right-size the libxpc effort it helps to know how complete the daemons we'd build on top are. Three audited here.

launchd

Reference Apple launchd-842.92.1 at apple-oss-distributions/launchd — the last open source, 2014. src/core.c alone is ~12k lines. Pre-libxpc-split: even Apple-2014 is behind modern macOS launchd, where job management migrated into closed libxpc.

TreeLoC (launchd proper)Mach / MIGStub markersState
freebsd-launchd (this project's port) 1,496 + 1,467 liblaunch none (intentional) 2 (documenting #if 0 for the corebase plist parser) partial — clean rewrite on libdispatch + AF_UNIX, boots, ~7% of Apple's body, supports daemon subset for FreeBSD as PID 1. Uses GNUstep's NSPropertyListSerialization for plist parsing.
NextBSD 16,391 + 4,661 liblaunch full (8 MIG .defs) 34 near-Apple-2014-parity on NextBSD's xnu-derived kernel. Won't build on stock FreeBSD. Dormant since 2016.
ravynOS 16,469 + 4,661 liblaunch full 36 NextBSD + ~150 lines of FreeBSD-15 fixes: <Availability.h> include, build flags, audit-session workaround, per-user env file. Squashed history (2025-09-28), so per-change provenance gone.

Critical missing pieces from freebsd-launchd: EnvironmentVariables, StartCalendarInterval, WorkingDirectory, UserName/GroupName, StandardOut/ErrorPath, Umask, ThrottleInterval, launchctl load -w, enable/disable, override-file mechanism. Per freebsd-launchd-plan's matrix — Phase 5 batch is the remaining work.

Decision-critical: The NextBSD/ravynOS launchd is closer to Apple parity but only runs on a Mach-coupled kernel. Now that our mach.ko exists, we have two ways to swap forward to the Apple-shape launchd:

Either way, the swap is enabled by libxpc and jumps us from 1,500 LoC of hand-coded subset to 16k+ LoC of battle-tested Apple-shape code.

configd / SystemConfiguration

Reference Apple apple-oss-distributions/configd — latest tag configd-1405.100.8 (2026-04-13). The framework (SystemConfiguration) lives inside the configd repo, not a separate one.

TreeSource present?State
freebsd-launchd/configd/ Yes — verbatim Apple configd-963.270.3 (133K LoC, 280 source files) demo / proof-of-concept — sources imported but only a 95-line GNUstep Obj-C banner stub (NCDaemon.m) compiles. The 133K-LoC Apple tree sits beside it, uncompiled.
ravynOS No absent — ravynOS has a rich frameworks/ tree (CF, AppKit, Foundation, CFNetwork) but no SystemConfiguration.
NextBSD No absent — never imported configd.

Subsystems present in the imported tree: SCDynamicStore (the key-value store), SCPreferences (plist-backed config), SCNetworkConfiguration/Service/Interface/Protocol/Set/Reachability, IPMonitor (19,181 LoC), KernelEventMonitor (FreeBSD ev_ipv4/ipv6/dlil bridge), LinkConfiguration, PreferencesMonitor, scutil CLI, dnsinfo, nwi, libSystemConfiguration (XPC client helper).

Gap to functional:

  1. MIG .defs missing from the import — configd_server.c, notify_server.c, _SCD.c all expect generated stubs.
  2. libxpc must be running first (452 xpc_* call sites). This is why libxpc is the unblocker for configd.
  3. 21 bootstrap_* / mach_port_* sites need real Mach support.
  4. FreeBSD bmake / cmake build for libSystemConfiguration.so, SystemConfiguration.framework, configd daemon, scutil. Currently zero of 280 source files are wired into the build.
  5. Plist schema files (PrivateStrings.plist, NetworkConfiguration.plist) need install rules.
  6. KernelEventMonitor BSD bridge (ev_ipv4.c/ev_ipv6.c) already uses PF_ROUTE/sysctl — FreeBSD-friendly, minor adapting only.
  7. Upgrade decision: staying on 963.270.3 (~2020) misses 6+ years of bugfixes / new subsystems (CategoryManager, netconfig, QoSMarking). Jumping to 1405.100.8 adds those but expands surface.

The good news: Apple's code in this tree is implemented, not stubbed (0 UNIMPLEMENTED, 1 TODO, 12 XXX). It's a verbatim Apple import. What's missing is everything around it: build glue, libxpc backend, MIG .defs, kernel-event-monitor adapters, launchd plist, the netconfigd driver that wires it together. Per freebsd-configd-plan's phasing.

IPConfiguration

Reference Apple apple-oss-distributions/bootp — latest tag bootp-534.100.6 (2026-04-17). The tarball ships multiple binaries: IPConfiguration (DHCPv4/v6/IPv4LL/RA/SLAAC client), IPConfiguration_framework, IPConfigurationHelper (XPC), ipconfig CLI, bootplib, bootpd (server, replaces CMU's), dhcp6d, rtadvd, NetBoot/BSDP. Total ~74k LoC for the in-scope client-side surface.

TreeSource present?State
freebsd-launchd No (Apple-side) paper plan onlyfreebsd-ipconfiguration-plan exists with DEFERRED pill. Today's livecd uses dhcpcd (Roy Marples) via org.freebsd.dhcpcd.plist. configd/src/SystemConfiguration.fproj/DHCP.c exists but it's the client-side SC API that reads DHCP info from the dynamic store — not a client.
ravynOS No absent — uses CMU bootpd + standard FreeBSD dhclient.
NextBSD No absent — same as ravynOS.

Apple deps that don't exist on FreeBSD (per freebsd-ipconfiguration-plan §7):

Recommendation: follow the existing plan — defer IPConfiguration porting until configd Phases 2/3 are real and libxpc is in place. For the livecd today, dhcpcd covers DHCPv4 + DHCPv6 + IPv4LL adequately and is mature/cross-platform. The estimated ~6-9 person-month cost of a full IPConfiguration port is best spent after configd's SCDynamicStore is functional, because IPConfiguration consumes SC keys for half its decisions.

9. GNUstep compatibility — libobjc2, corebase, Foundation

Project rule from scope_split: GNUstep owns the framework / application layer (libobjc2, Foundation, Base, GUI, Back). Apple source covers only the system-services layer (Mach, launchd, configd, notifyd, asl, libdispatch, libxpc, liblaunch). The userland stays ELF, not Mach-O. Question: does libxpc's port fit cleanly inside that rule, or does it force adopting Apple's ObjC runtime / Foundation?

libxpc itself fits cleanly. The fork we're using is pure C with zero Objective-C runtime dependency. GNUstep stays on the application side. You will not have to use Apple's libobjc.

But the surrounding stack has real gaps that need work. Audited 2026-05-11 against the gershwin-developer/Library/Sources/ tree:

libxpc itself

The ravynOS / NextBSD libxpc implementation (lib/libxpc/) compiles as plain C. From the API-surface audit:

So a libxpc consumer written in pure C (launchd, configd, asl, mDNSResponder) links -lxpc and never touches an Obj-C runtime through libxpc.

GNUstep apps consuming libxpc

A GNUstep / libobjc2 application that wants to talk to a launchd-managed service has two options:

  1. Call libxpc C API directlyxpc_connection_create_mach_service, set an event handler, send dictionaries. This is what launchd-managed daemons already use internally. No Foundation bridge needed.
  2. Write a GNUstep-Foundation NSXPCConnection for Cocoa-shape ergonomics. Not in scope for this plan but worth describing as an explicit downstream enhancement — see §9.4 below.

9.4. NSXPCConnection — downstream GNUstep enhancement, clean-room only

NSXPCConnection is Apple's Objective-C wrapper around libxpc. It provides protocol-based proxy objects, NSSecureCoding for arbitrary classes, automatic reply blocks, static type-checking at compile time, and invalidation / interruption handlers. Strictly an ergonomic layer over libxpc — doesn't add new system capability, but makes Cocoa code 10x more readable than raw libxpc message-construction.

Apple has never open-sourced NSXPCConnection. swift-corelibs-foundation deliberately skips it (no libxpc on Linux/Windows). It exists only inside macOS's closed-source Foundation.framework. "Importing" the class is not an option.

A clean-room reimplementation against the public Apple API documentation IS possible — same approach GNUstep has used for the rest of Foundation for ~30 years. Estimated scope: 3,000-5,000 lines of new Objective-C across NSXPCConnection, NSXPCInterface, NSXPCListener, the forwardInvocation:-based proxy machinery, the reply-block trampolines, and the NSSecureCoding ↔ xpc_object_t bridge.

Why not in this plan:

What it unlocks downstream:

How to encourage it: open an enhancement issue in gnustep/libs-base citing the libxpc availability on FreeBSD and the protocol/proxy machinery already present in NSDistantObject. Once the libxpc port stabilizes on FreeBSD and Darling-libxpc keeps improving on Linux, the case for a clean-room NSXPCConnection in upstream GNUstep gets stronger. Sequence-wise it doesn't matter when this lands — libxpc's C API works on its own, and the NSXPCConnection wrapper can come along whenever a GNUstep maintainer takes interest.

9.5. swift-corelibs-libdispatch — Mach RECV is unresolved on FreeBSD

Audit of gershwin-developer/Library/Sources/swift-corelibs-libdispatch/:

Path forward, ranked:

  1. Minimal viable backend (recommended). Add a new file, e.g. src/event/event_mach_freebsd.c, gated on __FreeBSD__. Define _dispatch_source_type_mach_recv using either: (a) a polling thread per source that calls mach_msg_trap(MACH_RCV_MSG | MACH_RCV_TIMEOUT) with timeout=0 and a wakeup mechanism, or (b) a kqueue user filter that the kernel signals when a Mach message arrives (would need an EVFILT_MACHPORT shim in mach.ko). Skip src/mach.c entirely — libxpc only uses DISPATCH_SOURCE_TYPE_MACH_RECV, not the dispatch_mach_t channel SPI.
  2. Full Mach backend. Add EVFILT_MACHPORT to FreeBSD kqueue in mach.ko, then flip HAVE_MACH=1. Still need stubs for mach_port_construct, dead-name notifications, vouchers — most of src/mach.c won't link without them. Multi-week scope.

Phase 0 verification (the simple "does it work?" test) won't pass as-is. Gershwin's libdispatch needs at minimum a new backend file (Path 1 above), which is ~500-1000 lines of code. This is real work, not a build-flag flip. Plan accordingly.

9.6. libs-corebase — covers value types, gaps on Mach IPC

Audit of gershwin-developer/Library/Sources/libs-corebase/ vs freebsd-launchd/configd/ (the 126,707-LoC verbatim Apple configd import):

libs-corebase scope: GNUstep CoreBase, version 0.2, LGPL. Standard GNUstep build (configure.ac, Headers/CoreFoundation/, Source/). Last meaningful activity 2021-09 / 2022-09 — primarily Windows build fixes and ICU API updates. Stable, partial-by-design, not actively developed.

CF areacorebaseconfigd impact
CFString / CFDictionary / CFArray / CFNumber / CFData / CFDate / CFTimeZone / CFCalendarPRESENT ICU-backed, ~5,500 String calls / 1,900 Dict / 1,800 Array in configd should all linkCompiles
CFPropertyList (XML + binary plist)PRESENT 1561 lines, both directionsSCPreferences plist parsing works
CFURL / CFCharacterSet / CFUUID / CFXMLParser / CFLocale / CFNumberFormatterPRESENTFine
CFRunLoopPARTIAL sources0 + timers + observers work; CFRunLoopProcessSourcesVersion1 is a literal TODO stub at CFRunLoop.c:567-585BREAKS configd IPCserver_loop() in configd_server.c won't dispatch Mach messages
CFMachPortABSENT no header, no sourceBREAKS configd IPC — 80 calls across configd_server.c, session.c, _configopen.c, dns-configuration.c, SCDPlugin.c, SCDPrivate.c, SCHelper_server.c. ~9 functions need implementing (Create, CreateWithPort, CreateRunLoopSource, GetPort, Invalidate, ...).
CFPreferencesABSENTBREAKS SCPreferences / PreferencesMonitor plugin — ~22 calls
CFBundlePARTIAL 268-line NSBundle-backed shim; ~22 functions but missing CFBundleCreateBundlesFromDirectory, CFBundleCopyLocalizedString, CFBundleCopyNonLocalizedString, _CFBundleCachedInfo*BREAKS configd plugin loader in plugin_support.c. Also drags in GNUstep ObjC base as transitive dep — contradicts "configd is plain C against CF".
CFPlugIn / CFUserNotification / CFHost / CFHTTPMessage / CFXPCABSENTPeripheral subsystems — can #ifdef out for headless configd
CFNotificationCenterABSENTconfigd does not use it — N/A

Plus Apple-private CF extensions used by configd that exist nowhere open-source except Apple's CF-Lite drop: CFStringIsValidNetBIOSName, CFStringIsValidDNSName, CFStringCreateWithInAddr, CFStringCreateWithIn6Addr, CFDataCopyVMData, CFDataCreateVMData, CFStringGetInstallationEncodingAndRegion, CFXPCCreateCFObjectFromXPCObject, _CFBundleCachedInfo*.

Recommended path: hybrid corebase + CF-Lite

Use libs-corebase for the value-type half (String/Dict/Array/Number/Data/Date/PropertyList/URL/Calendar/Locale). Pull CFMachPort.c, CFRunLoop.c (or just version1 dispatch), CFBundle.c, CFPreferences.c from Apple's last open-source CF-Lite (CF-1153.18, ~2017, opensource.apple.com). Write a thin shim for the half-dozen private _CF helpers configd uses.

CF-Lite is plain C — not Foundation, not Objective-C. Importing it does NOT violate the scope_split rule against adopting Apple's ObjC/Foundation. It's the same shape of import as the configd source you already have at freebsd-launchd/configd/.

This hybrid:

New work estimate to make configd link: ~3,000-5,000 lines of new/imported code — CF-Lite's CFMachPort + CFRunLoop version1 + CFBundle + CFPreferences + the private CF extension shims. This becomes a separate sub-project (call it libCoreFoundation-freebsd) inside or alongside freebsd-launchd, similar to how the configd source already lives there.

Decision summary

LayerRuntimeSource
libxpcpure CiXsystems/Klama reimpl — no ObjC, no CF
libdispatchpure C + blocksgershwin-developer install
libnv (bundled with libxpc)pure CBSD libnv, bundled in libxpc tree
liblaunchpure Cfreebsd-launchd's existing version
launchd daemonpure Cfreebsd-launchd (current minimal); Phase 5 swaps to clean Apple launchd-842.92.1 import
configd daemonC + CoreFoundationApple verbatim — CF supplied by hybrid libs-corebase (value types) + CF-Lite (CFMachPort, CFRunLoop v1, CFBundle, CFPreferences); see §9.5
configd's small ObjC shimGNUstep libobjc2NCDaemon.m already uses GNUstep runtime
SystemConfiguration frameworkC + CoreFoundationApple verbatim — same CF dependency as configd
asl / mDNSResponder / notifydpure CApple sources (no CF dependency in these)
GUI / app layer / FoundationGNUstepApple source explicitly NOT adopted here

Where you'd be forced into Apple's ObjC: only if a future downstream wants NSXPCConnection's Cocoa-shape API. None of the daemons in this porting scope (launchd, configd, asl, notifyd, mDNSResponder, IPConfiguration) need it. GNUstep apps that want XPC can call libxpc's C API directly. No conflict with the rule.

10. Libsystem strategy — port sub-libraries, never the umbrella

Strategic question that comes up once you've ported libxpc and start eyeing the next daemons: should we port Libsystem from macOS? Apple's apple-oss-distributions/Libsystem exists on opensource.apple.com (latest tag Libsystem-1356, 2025-10-04). It sits very low in the macOS stack — below CoreFoundation, below Foundation, below libxpc itself. Tempting to think "if we're porting Apple's userland, we might as well port the base of it."

Verdict: don't port Libsystem as a whole. It's an umbrella, not a library. Port the individual sub-libraries we actually need (libxpc, libnotify, libasl); keep FreeBSD's libc, rtld, malloc, and pthread underneath. No Apple ObjC or Foundation involved at this layer.

10.1. What Libsystem actually is

On macOS, /usr/lib/libSystem.dylib doesn't have its own code — it's an umbrella that reexports symbols from a dozen sub-libraries:

Apple sub-libraryWhat it providesOur equivalent / plan
libsystem_cApple's libc forkFreeBSD libc — never replace
libsystem_pthreadPOSIX threadsFreeBSD pthread — never replace
libsystem_dyldApple's dynamic loader runtime supportFreeBSD rtld — never replace (replacing dyld means switching to Mach-O, breaking ELF userland; scope_split forbids this)
libsystem_mallocApple's nano/scalable mallocFreeBSD jemalloc — never replace
libsystem_kernelSyscall wrappers + Mach trap stubsThis is what our libmach is becoming (Phase C1 done)
libsystem_dispatchlibdispatch / GCDswift-corelibs-libdispatch + our Mach backend (Phase 0 of this plan)
libsystem_xpclibxpcThis plan (Phases 2-4)
libsystem_notifylibnotify (notifyd client)Future port from apple-oss-distributions/Libnotify, after notifyd
libsystem_asllibasl (Apple system logger client)Future port from apple-oss-distributions/libasl
libsystem_platformLow-level Apple-specific (atomics, OSSpinLock, pthread_mutex_lock_with_options)Don't port — FreeBSD equivalents in libc / pthread
libsystem_blocksBlocks runtimelibBlocksRuntime (already in ports / gershwin)
libcommonCryptoApple's CommonCrypto APIDon't port — use OpenSSL or FreeBSD's libmd

The Libsystem repo itself is mostly a linker stub specification — a .dylib stub that says "I reexport these symbols at these versions from these sub-libraries." It's metadata, not code. There's no implementation to port; there's just a list of what to glue together.

10.2. Can Apple's libsystem_c and FreeBSD libc coexist?

No. A process has exactly one libc. The libc your binary loads provides the standard C runtime (malloc, stdio, errno, fork, exec), and you can't have two of those active simultaneously — they'd fight over malloc arenas, signal dispositions, errno location, syscall ABIs (Apple's libc speaks Mach traps + BSD syscalls with Apple's numbering; FreeBSD libc speaks FreeBSD's syscall ABI).

Replacing FreeBSD libc with Apple's libsystem_c would mean:

However, you absolutely can have FreeBSD libc as your libc AND link in Apple's individual API-only sub-libraries (libdispatch, libxpc, libnotify, libasl) alongside. Those sub-libraries call libc through standard C APIs — they work against any libc, whether Apple's or FreeBSD's. That's exactly the model we're already using. Our libmach links FreeBSD libc; libxpc will too; configd will too. All of them happily call malloc(), printf(), open(), etc. through FreeBSD's libc.

10.3. Does Libsystem require Apple's ObjC or Foundation?

No. Libsystem is pure C and sits below Foundation/CF/ObjC in the macOS stack. The dependency direction is:

NSXPCConnection / Cocoa apps         (ObjC)
        |
        v
Foundation.framework                  (ObjC)
        |
        v
CoreFoundation.framework              (C)
        |
        v
Libsystem (libxpc, libdispatch, ...)  (C)  <-- libxpc lives here, below CF/Foundation
        |
        v
FreeBSD libc / kernel                 (C)

Foundation builds on top of Libsystem. Libsystem doesn't know Foundation exists. Same for CoreFoundation — CF uses Libsystem's libdispatch and libxpc internally, but Libsystem doesn't depend on CF.

So adopting Libsystem (or rather, individual Libsystem sub-libraries) does NOT pull in Apple's ObjC runtime or Foundation. The scope_split rule (GNUstep for framework layer, Apple source only for system services) is preserved exactly.

10.4. Would future ports need Libsystem?

No, only the sub-libraries we already plan to port. The daemons we want (configd, mDNSResponder, notifyd, asl, IPConfiguration) link against the individual API libraries on macOS — -lxpc -ldispatch -lc. On FreeBSD we'd patch each daemon's Makefile to do the same with our libraries: -lxpc -ldispatch -lmach -lc (FreeBSD libc instead of Apple's). One-line Makefile change per daemon. We're already touching every daemon's Makefile anyway.

On macOS, daemons sometimes link -lSystem (the umbrella) as shorthand for "I want everything." On FreeBSD that shorthand isn't available, but it's easy to recreate with a tiny stub library if it ever becomes ergonomically painful (~10 lines of a linker script that pulls in our actual libraries). Deferrable indefinitely.

10.5. What does this mean for the porting plan?

Bottom-line rule: when looking at any new Apple-source porting decision, ask "is this a Libsystem sub-library that's an API-only consumer of libc, or is it something deeper that wants to replace libc / dyld / malloc?" The former is portable; the latter is off-limits.

11. Risks and decisions

Fork point

Decision: fork libxpc from ravynos/lib/libxpc/. NextBSD's tree is older and explicitly self-described as a first pass; ravynOS has substantive object-model + serialization fixes. Diff is small enough (~150 lines) that we won't regret missing NextBSD's path.

Apple parity

Cannot reach modern Apple parity. Ever. Even with this port, we are upper-bounded by Apple-2014's public API surface plus whatever third-party reverse-engineering (e.g. Darling's activity-key additions) we adopt. Modern macOS XPC features — xpc_activity beyond keys, post-Yosemite domain model, NSXPCConnection ObjC bridge, codesign-derived entitlement enforcement — are out of reach without either Darling-level Obj-C runtime work or rewriting from scratch.

Wire format incompatibility

Acceptable. libnv-over-Mach instead of bplist-over-Mach means we can never bit-exchange messages with real macOS apps. Fine — our stack is FreeBSD-native binaries top-to-bottom. Compatibility is at the source level, not the wire level.

libdispatch's Mach receive source

Confirmed broken — needs a new backend. Per the audit in §9.4: gershwin's libdispatch is pristine swift-corelibs-libdispatch with HAVE_MACH off; DISPATCH_SOURCE_TYPE_MACH_RECV is unresolved at link. Force-enabling fails because EVFILT_MACHPORT doesn't exist in FreeBSD kqueue and src/mach.c (3,250 lines) assumes XNU's full Mach ABI. A new backend file (~500-1000 lines, polling thread or kqueue-user-filter wrapping mach_msg_trap(MACH_RCV_TIMEOUT=0)) is required. This is Phase 1 of the plan, not a "verify and proceed" check.

libs-corebase coverage of configd's CF needs

Significant gaps — hybrid with Apple CF-Lite required. Per the audit in §9.5: corebase has CFMachPort absent, CFRunLoop version1 sources unimplemented (TODO stub), CFPreferences absent. Roughly 3,000-5,000 lines of new/imported code needed — pulled from Apple's open-source CF-Lite (plain C, not ObjC). This is its own sub-project, sequenced before Phase 6 (configd functional).

Bootstrap server complexity

Real work. bootstrap_check_in / bootstrap_look_up need a userspace name service. Apple's launchd is the bootstrap server — the bootstrap subsystem is internal to the launchd daemon. Our current freebsd-launchd minimal rewrite doesn't have it; the clean Apple launchd-842.92.1 import in Phase 5 will. Either we stand up a small standalone bootstrap server in Phase 3 (smaller, faster, gets libxpc tested), or we wait for Phase 5's full Apple launchd to bring the bootstrap server along with it. The plan recommends both: Phase 3 minimal standalone for testing, Phase 5 swap to Apple's full launchd for production.

"Should we just use D-Bus or AF_UNIX everywhere?"

Already decided: no. The whole project's premise is that Apple's system-service plumbing — including XPC's connection-based-async-with-typed-dictionaries shape — is what we want to modernize FreeBSD toward. Substituting another IPC mechanism breaks source compatibility with every daemon we want to inherit. The cost of libxpc is the price of admission.

11. Phased porting plan

Phase 0 — libdispatch Mach backend (1-2 weeks)

This is the first task. The 2026-05-11 audit (§9.4) confirmed gershwin's swift-corelibs-libdispatch has no Mach RECV support on FreeBSD. Without it, libxpc connections can't deliver incoming messages — every xpc_connection_set_event_handler registration would dangle. Everything downstream of libxpc (configd, swapped launchd, ...) depends on this working first.

Delivery mechanism: new patch in gershwin-developer's Patches/ directory

gershwin-developer already follows a clean patches/clone pattern: Library/Sources/swift-corelibs-libdispatch/ holds a pristine upstream clone, and Library/Patches/swift-corelibs-libdispatch.patch (4 hunks of FreeBSD kqueue/timer/workqueue fixes) gets applied at build time. We add a second patch file alongside the existing one:

gershwin-developer/Library/Patches/
├── swift-corelibs-libdispatch.patch         (existing, kqueue fixes)
└── swift-corelibs-libdispatch-mach.patch    (NEW, our Mach backend)

The new patch is exactly two hunks:

  1. A new file src/event/event_mach_freebsd.c (~500-1000 lines)
  2. One line added to CMakeLists.txt's target_sources(...) list, gated on __FreeBSD__

Why a separate patch (not folded into the existing one): different bug surface, different reviewers, different upstream pathway. Gershwin's existing patch fixes pre-existing FreeBSD kqueue bugs they already maintain; ours adds a brand-new source-type backend. Keep them apart.

Why patches not in-place edits: gershwin-developer's build re-clones Sources/swift-corelibs-libdispatch/ from upstream each build. Hand-edits to the clone get clobbered. Patches survive.

Implementation contents

  1. Define _dispatch_source_type_mach_recv in event_mach_freebsd.c, gated on __FreeBSD__. Implementation choice:
    • Option A (simpler, recommended): per-source polling thread that loops on mach_msg_trap(MACH_RCV_MSG | MACH_RCV_TIMEOUT, timeout=small) and invokes the registered event handler via the source's target queue. Pros: no kernel changes. Cons: per-source thread overhead.
    • Option B (cleaner): a single dispatch worker thread reading a kqueue user-filter that mach.ko signals on message arrival. Requires extending mach.ko with an EVFILT_MACHPORG-equivalent shim. Pros: efficient, single thread. Cons: kernel work.
    Start with Option A; revisit B once profiling shows the per-source thread cost matters.
  2. Do NOT enable HAVE_MACH=1. The 3,250-line src/mach.c assumes XNU Mach ABI we don't have. Keep that codepath excluded; our backend lives entirely in the new event_mach_freebsd.c.
  3. Link against libmach (the Phase C1 library) for the syscall wrappers.

Dev iteration workflow on the development host

The patch isn't idempotent — applying twice fails. Each iteration starts from a pristine upstream clone:

WORK=/root/libdispatch-dev
rm -rf "$WORK"                                  # always start clean
mkdir -p "$WORK" && cd "$WORK"
git clone --depth 1 https://github.com/apple/swift-corelibs-libdispatch.git
cd swift-corelibs-libdispatch

# Apply both patches in order:
patch -p1 < /root/Patches/swift-corelibs-libdispatch.patch
patch -p1 < /root/Patches/swift-corelibs-libdispatch-mach.patch

# Build:
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug \
         -DCMAKE_INSTALL_PREFIX=/usr/local
make -j$(sysctl -n hw.ncpu)
make install   # to /usr/local/lib, NOT /System/Library/Libraries
               # (avoids collision with gershwin's libdispatch during dev)

# Test: a small C program that:
#   1. mach_reply_port() to get a port
#   2. dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, port, 0, queue)
#   3. dispatch_source_set_event_handler(...)
#   4. another thread sends mach_msg to the port
#   5. verify handler fires within timeout
# Link against -lmach -ldispatch

Patches live locally at gershwin-developer/Library/Patches/; sync to the development host via scp before each iteration (rsync may not be available; scp is universally fine).

Promotion path

  1. Dev on the development host → test program runs green
  2. Commit patch to pkgdemon's gershwin-developer fork
  3. PR to upstream gershwin-developer
  4. Until upstream merges, gershwin-on-freebsd's build.sh clones from pkgdemon's fork

Smoke tests — proof of life before any libxpc work begins

Phase 0 isn't done until these all PASS. They're the contract libxpc will build on; ship none of Phase 2 until every test below is green.

Prerequisite (small Phase 1 spillback): the smoke tests need a SEND right on our own port, which means mach_port_allocate and mach_port_insert_right have to be wired in mach.ko first. Those are early Phase 1 items in the original plan; pull them forward into Phase 0 so the smoke is real:

Then the libdispatch-Mach-recv test program tests/test_dispatch_mach_recv.c, linked against the patched libdispatch + libmach. The program runs five subtests, each printed as a PASS/FAIL line:

#Smoke checkWhat it proves
1 dispatch_source_create returns non-NULL — create a port via mach_port_allocate(MACH_PORT_RIGHT_RECEIVE), call dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, port, 0, queue). Check non-NULL. Backend's source-type constructor is wired and returns a real source object.
2 Handler fires on single message — allocate port, insert MAKE_SEND right on same port, attach source with handler that increments a counter, dispatch_resume the source, mach_msg(SEND) a header-only message to the port. Wait up to 1s. Assert counter == 1. Backend correctly polls/waits on the port, decodes the message, and invokes the handler on the target queue. The actual "Mach dispatch works" proof.
3 Handler fires N times for N messages — same setup, send 100 messages in a tight loop, wait up to 5s for counter to reach 100. Backend doesn't drop messages under load; queue and serialization are correct.
4 Cancellation is clean — attach source, register a cancellation handler that sets a flag, dispatch_source_cancel, wait for cancellation handler to fire. Send a message after cancel and confirm the event handler does NOT run. Backend supports clean teardown. libxpc relies on this for connection invalidation.
5 No port-table leak — capture sysctl mach.stats.ports_in_use before, run the previous tests 10 times in a loop, capture after. Difference should be near zero (some baseline noise OK). Backend properly deallocates Mach ports on source destruction. Catches the most common refcount-leak class of bug.

Plus regression: all 21 existing smokes (after the two new port-management syscalls are added) still PASS. The patched libdispatch must not break the existing kqueue path used by the libxpc-less rest of the system.

Total Phase 0 smoke target: 26 checks (21 existing + 5 dispatch-mach-recv). Once green, the libxpc fork (Phase 2) starts.

Phase 0 sequence summary

  1. Wire 2 new mach.ko syscalls (port_allocate, port_insert_right) + libmach wrappers + 2 smoke checks. ~2 days.
  2. Write swift-corelibs-libdispatch-mach.patch with event_mach_freebsd.c + CMakeLists.txt entry. ~1 week.
  3. Iterate on the development host using the reclone-every-time loop above until test_dispatch_mach_recv goes 5/5 green. ~2-5 days.
  4. Wire test_dispatch_mach_recv into smoke-sysctls.sh. Confirm 26/26.
  5. Commit patch to pkgdemon's gershwin-developer fork; PR upstream to gershwin-developer.
  6. Document Phase 0 completion in PHASE_C_FINDINGS.md in freebsd-launchd-mach.

Phase 1 — mach.ko expansion for libxpc (1-2 weeks)

Add the missing Mach port-management syscalls to freebsd-launchd-mach/src/mach_kmod/, following the wired-trap pattern established for the trap family + mach_msg:

Deliverable: smoke harness grows from 19/19 to ~26/26.

Phase 2 — libxpc fork + minimal build (1 week)

  1. Snapshot ravynos/lib/libxpc/ into freebsd-launchd-mach/src/libxpc/. Take the bundled subr_nvlist.c + subr_nvpair.c too (don't try to use FreeBSD's stock libnv until later — the bundled version has XPC-specific tweaks).
  2. FreeBSD bsd.lib.mk Makefile. Install path: /usr/local/lib/libxpc.so, headers to /usr/local/include/xpc/ (PREFIX defaults to /usr/local, matching the libmach Makefile from Phase C1). Standard FreeBSD ports convention. Not gershwin-coupled; this ships alongside libmach as an out-of-tree port that works on any FreeBSD with our mach.ko loaded.
  3. Build clean. Expect this to surface missing libmach symbols (bootstrap_port, mach_port_allocate) — that's fine, link with weak undefined symbols or stub for now.
  4. Write a minimal smoke test: xpc_dictionary_create, xpc_dictionary_set_int64, xpc_dictionary_get_int64, xpc_release. No Mach yet, just in-process object plumbing. Verify it builds and runs.

Install-path decision

Three precedents in our ecosystem:

PathWhere it's usedNotes
/usr/lib/system/libxpc.dylibApple macOSInside Libsystem composite. Reexported via /usr/lib/libSystem.dylib. Not under /System/Library/. macOS reserves /System for .framework bundles, not loose .dylibs.
/usr/lib/libxpc.soravynOSStandard FreeBSD base-system. bsd.lib.mk defaults applied (no LIBDIR override).
/usr/local/lib/libxpc.soThis project (recommended)Standard FreeBSD ports convention. Matches the libmach Makefile placement chosen in Phase C1. Ships as an out-of-tree port that works on any FreeBSD with our mach.ko loaded.
/System/Library/Libraries/libxpc.so(gershwin pattern, not used)Where gershwin-developer puts libdispatch / libobjc / libgnustep-base / libgnustep-corebase. Rejected: couples this project to gershwin's layout choices; /System on macOS is reserved for .framework bundles, so this path is gershwin-specific rather than Apple-compatible.

Decision: /usr/local/lib/libxpc.so with headers at /usr/local/include/xpc/. Consistent with libmach (Phase C1) and with standard FreeBSD third-party port placement. Gershwin's ISO build picks this up via its normal package-install pipeline; no special path coupling.

Phase 3 — bootstrap server (2-3 weeks)

The hard part. Options, in order of effort:

  1. Option A: minimal standalone bootstrap server. Write a tiny daemon that owns the bootstrap port, accepts bootstrap_register / bootstrap_look_up over Mach, stores port-name mappings in memory. ~500-1000 lines. Reuses NextBSD's MIG .defs for the bootstrap protocol.
  2. Option B: extract just the bootstrap subsystem from Apple's launchd — pull job_mig_dispatch + bootstrap port handling out of launchd-842.92.1 source, ship it as a standalone daemon. Maybe 2000-3000 lines. Closer to Apple-shape but heavier.
  3. Option C: full clean Apple launchd import. Phase 5's plan: import launchd-842.92.1 verbatim from apple-oss-distributions/launchd, build against our libmach + libxpc + libdispatch with a patch series for FreeBSD adaptations. Highest payoff (16k+ LoC of upstream-clean Apple code), highest risk (most surface to debug). This becomes the production launchd.

Recommended Phase 3 scope: Option A. Smallest critical path to "libxpc connections actually work end-to-end." The full Apple-launchd import comes in Phase 5 once libxpc is proven.

Phase 4 — xpc_connection end-to-end test (1 week)

  1. Write a test program that registers a service name with the bootstrap server, accepts a connection, receives a dictionary, sends a reply.
  2. Write a client that connects via xpc_connection_create_mach_service, sends a dictionary, receives the reply.
  3. Verify ports are cleaned up on disconnect, no leaks in mach.stats.ports_in_use.
  4. This is the libxpc equivalent of C3's "userland mach_msg works end-to-end" milestone.

Phase 5 — Apple launchd-842.92.1 clean import (4-8 weeks)

Replace freebsd-launchd's 1,496-LoC minimal rewrite with a clean verbatim import of Apple's last open-source launchd, building against our libmach + libxpc + libdispatch stack and keeping all Mach paths intact (no stripping, unlike freebsd-launchd's original approach).

Why this over a ravynOS fork: same source code either way (ravynOS's launchd descends from NextBSD which descends from Apple launchd-842.x), but a clean import gives us:

Layout (mirrors configd's import pattern):

freebsd-launchd-mach/
├── src/sbin/launchd/
│   ├── (Apple verbatim from launchd-842.92.1)
│   ├── core.c                     (~12k lines)
│   ├── runtime.c, launchd.c, ipc.c, log.c
│   ├── *.defs                     (MIG protocol_jobmgr, job, job_reply, ...)
│   └── ...
├── scripts/import-launchd.sh      (refresh-from-upstream tool)
└── patches/launchd/
    ├── 0001-freebsd-15-availability-header.patch    (~20 lines)
    ├── 0002-build-flags-pic-no-werror.patch          (~10 lines)
    ├── 0003-audit-session-self-workaround.patch      (~30 lines)
    ├── 0004-link-against-libmach-libxpc-libdispatch.patch
    ├── 0005-freebsd-mach-compat-shim.patch           (the bulk)
    ├── 0006-mig-defs-for-freebsd-makefile.patch
    └── 0007-per-user-env-file.patch                  (~45 lines; useful ravynOS feature)

Estimated patch size: ~700-1500 lines sitting on top of Apple's ~16,500 lines of clean source. Comparable to gershwin's libdispatch patch in approach, larger in absolute size.

What the patches must do:

  1. FreeBSD bsd.prog.mk Makefile replacing Apple's xcconfig. ~50-100 lines.
  2. MIG .defs compilation — Apple's protocol_jobmgr.defs, job.defs, etc. via a FreeBSD-host mig tool producing our shape of stubs. ~100-200 lines of build orchestration.
  3. Mach API compat shims — Apple's launchd assumes the full XNU Mach ABI (bootstrap_*, mach_port_construct, MACH_NOTIFY_DEAD_NAME, voucher APIs). Our libmach exposes a subset (Phase 1 expansion + Phase 3 bootstrap server). Either widen libmach to cover what launchd needs, or write thin compat shims that route to what we have. ~500-1000 lines.
  4. <Availability.h> shim — Apple's headers reference __OSX_AVAILABLE_STARTING(...); trivial macro stubs. ~20 lines.
  5. Audit-session workarounds — same _audit_session_self() issues ravynOS hit on FreeBSD 15. ~30 lines.
  6. Per-user env file_read_env_file() that ravynOS added; useful for FreeBSD-style /etc/launchd_user.env. ~45 lines.

Dependencies on prior phases:

Reference NOT fork: NextBSD's sbin/launchd/ and ravynOS's sbin/launchd/ are useful prior art — their FreeBSD shims have already solved many of the same problems we'll hit. We can read their patches as guidance, but our patches stay clean-room (commit messages describe the underlying problem, not "ported from NextBSD"). Keep the lineage cleanly "Apple upstream + our patches."

Test gate (matches Phase B Tier 1's smoke-driven completion criterion):

  1. launchd boots as PID 1 on a livecd
  2. Processes org.freebsd.* launch.plists from /System/Library/LaunchDaemons/
  3. Can start/stop a daemon via launchctl load / launchctl unload
  4. Bootstrap port-name registration / lookup works (proves the libxpc + bootstrap server integration)
  5. System reaches multiuser without panic across at least 100 boot cycles

Cutover: ISO build switches from freebsd-launchd to freebsd-launchd-mach launchd. Keep freebsd-launchd's minimal daemon around as a fallback for non-Mach-aware deployments (e.g. a stripped-down FreeBSD build that doesn't load mach.ko).

Phase 5.5 — CoreFoundation hybrid (libCoreFoundation-freebsd) (3-6 weeks)

Prerequisite for Phase 6. Per the audit in §9.5, GNUstep libs-corebase has critical gaps that block configd. Build a hybrid CF library that ships with freebsd-launchd:

  1. Inventory the libs-corebase symbol set; mark coverage of each CF function configd uses.
  2. Import Apple CF-Lite (CF-1153.18, ~2017) from opensource.apple.com into freebsd-launchd/CoreFoundation/. Plain-C-only; CF-Lite has no Foundation / ObjC content. Drop any sources that overlap cleanly with corebase (CFString, CFDictionary, CFArray, CFNumber, CFData, CFPropertyList) and keep what fills gaps: CFMachPort, CFRunLoop (or just the version1 dispatch path), CFBundle, CFPreferences, plus private CF extensions.
  3. Write a thin shim for the ~6 Apple-private CF extensions configd uses (CFStringIsValidDNSName, CFDataCopyVMData, etc.). Pull from CF-Lite where present; stub the rest.
  4. Build artifact: libCoreFoundation.so linking corebase value-types + CF-Lite runtime parts + shim. Install to /usr/local/lib with headers to /usr/local/include/CoreFoundation/.
  5. Test: link a minimal program that calls CFMachPortCreateWithPort, attaches it to CFRunLoopRunInMode, sends a Mach message from another thread, verifies the version1 source callback fires. This is the equivalent of Phase 0 but at the CF layer.
  6. Decide on upstreaming: contribute the new CFMachPort / CFRunLoop version1 implementations to libs-corebase upstream once stable. The CF-Lite-imported parts can't be upstreamed (license / lineage) and stay in our tree.

Phase 6 — configd functional (4-8 weeks)

Depends on libxpc (Phase 4) and CoreFoundation hybrid (Phase 5.5).

  1. Generate the missing MIG .defs for configd_server, notify_server, _SCD.
  2. Build libSystemConfiguration.so + SystemConfiguration.framework + configd + scutil via bmake. Link against the Phase 5.5 libCoreFoundation.so and our libxpc.
  3. Wire KernelEventMonitor's ev_ipv4.c/ev_ipv6.c against PF_ROUTE.
  4. Stand up a minimal SCDynamicStore service; cut over the launchd plist for it.
  5. Decide: stay on configd-963.270.3 or rebase to configd-1405.100.8. Recommend staying on 963 for now; rebase becomes a separate plan once SCDynamicStore + SCPreferences are running.

Phase 7+ — IPConfiguration, mDNSResponder, notifyd, asl

Out of scope for this libxpc plan but unblocked by it. Each daemon's existing pkgdemon.github.io plan (ipconfiguration, mdnsresponder, notifyd, asl) takes over once libxpc + configd are real.

12. References & key paths

Local trees

Upstream

Related plans

Last updated 2026-05-11. Research compiled from six parallel agent surveys against the local NextBSD, ravynOS, and freebsd-launchd trees plus web research against the apple-oss-distributions and ravynsoft GitHub orgs. Every URL has been verified; every LoC number is from a wc -l over the cited path. Where research couldn't determine an answer (e.g. "which exact macOS SDK version were the captured headers from"), the document says so explicitly rather than guessing.