← Back · evidence in Mach IPC — what has been eliminated

Dropping libxpc and libdispatch

Can NextBSD ship pre-XPC, open-source Apple components instead of maintaining reimplementations? Scoping, with the parts that are measured separated from the parts that are not.

Drafted 2026-09-01, after the Mach IPC wedge was fixed. The component survey has not been run yet — Phase 0 below.

Status: this is a scoping document, not an approved plan. The per-component 10.6 survey that would make it actionable is Phase 0 and has not been done. Everything below marked measured comes from this tree or from today's debugging; everything marked assumed needs checking before anyone commits to it.

1. Why this is on the table

Two boot-path failures were diagnosed today, and neither was a Mach defect:

FailureCauseLayer
amd64: no logging at all at bootasl_trigger_aslmanager() does a synchronous XPC round-trip to a launchd demand-launched service, waiting XPC_SYNC_REPLY_TIMEOUT_NSEC (30s), from write_boot_log() before /var/run/log existslibxpc
attempted fix killed syslogd outrightxpc_connection_send_message() is dispatch_async onto the connection's send queue — from syslogd's bsd_in recv pthread, the pattern asl_action.c:1765 already documents as SIGSEGVinglibdispatch

The wedge that started the whole investigation was not libxpc — it was EV_CLEAR on a kqueue readiness knote in libmach. That matters: the case for this effort rests on the two boot-path failures above, not on the wedge.

2. What is actually a reimplementation

ComponentUpstream statusMaintenance cost here
libxpcNever open-sourced by AppleEntirely ours. No upstream to track, no reference to diff against.
libdispatchOpen source, but portedUpstream carries no HAVE_MACH for this platform; the Mach path here is original work.
libmachPartially trackableTwo layers with no upstream ever; headers and error tables have one. See §2b.

2b. Correction: libmach has an upstream — it is just not called libmach

Correction. The table above previously classified libmach as a reimplementation whose maintenance cost was “unavoidable — it is the project,” putting it in the same bucket as libxpc. That was wrong. libxpc has no upstream at any vintage. libmach has a real, licence-compatible one that simply is not published under that name — and nobody is tracking it.

Verified (survey, 2026-09-01): a search of apple-oss-distributions returns zero repositories named libmach. There is no Apple project by that name and never was; the name descends from CMU Mach 3.0 and NeXTSTEP. But the Mach userland is open source and always has been — it lives in xnu/libsyscall/mach (67 files, built into libsystem_kernel.dylib) with its header vocabulary in xnu/osfmk/mach.

Licensing is a non-issue: mach_error_string.c and mig_reply_setup.c carry dual APSL 2.0 + Carnegie Mellon Mach headers, the three .defs already vendored into src/libmach carry APSL 2.0, and NOTICE.md already records libmach → libsystem_kernel, APSL-2.0 alongside bootstrap_cmds, configd, libIOKit, kext_tools, Libnotify and syslog. Adopting from libsyscall introduces no new licence family.

The “43 files” figure was misleading

An earlier survey reported libmach as 40-of-43 files NextBSD-original. Arithmetically true, materially misleading: those 43 files are 2 .c files (2,636 lines), 3 already-vendored Apple .defs, 37 headers, a Makefile and a .pc. The hand-maintained code is concentrated in two files, and the header bulk — the larger part of the count — has an Apple upstream the survey missed.

What Apple gives away that we hand-stubbed

The standout is the error-table family. include/mach/mach_error.h currently ships this:

static __inline const char *
mach_error_string(mach_error_t error_value)
{
	(void)error_value;
	return "(mach error — see <mach/kern_return.h> for the code)";
}

mach_error_type() likewise returns "(unknown)", and the header admits it: “We don’t ship those tables… replace the inline with a real table-driven implementation.” Apple ships exactly that table — roughly 50 KB across mach_error_string.c, mach_error.c, error_codes.c, errorlib.h and eight .sub tables (err_ipc, err_mach_ipc, err_kern, err_vm, err_server, err_us, err_iokit, err_libkern). mach_error_string.c references only _mach_errors and err_max_system — zero port, task or syscall dependency, so it is a pure vendor-and-link with no exposure to the port-name divergence below. It upgrades every mach_error_string() call site in libbootstrap and launchd from a constant string to real diagnostics.

Four smaller items are the same shape — Apple has them, we either hand-wrote or stubbed them, and none touch port representation: mig_reply_setup.c (six lines of field copying; not implemented here at all), mig_strncpy.c (hand-written at mach_traps.c:937), and port_descriptions.c / slot_name.c (right-type→string, nothing here). One larger: mach_msg_server_once, whose in-tree version returns MACH_RCV_TIMED_OUT unconditionally with the comment “for Phase I1c we don’t run the helper-downcall path.” Apple’s real one is in mach_msg.c.

The one header that must never be vendored

Port names are file descriptors here, and include/mach/port.h inverts Apple’s bit layout deliberately:

#define MACH_PORT_INDEX_MASK	0x00ffffffU
#define MACH_PORT_INDEX(name)	((mach_port_name_t)(name) & MACH_PORT_INDEX_MASK)
#define MACH_PORT_GEN(name)	((mach_port_name_t)(name) & 0xff000000U)

The generation goes in the high byte and the index in the low 24 bits, “because a port name is currently also a file descriptor and the index half has to stay a valid fd number.” Apple’s layout is the opposite. Taking Apple’s port.h would not fail to compile — it would silently corrupt hashing, exactly the way the previously-wrong macro made MACH_PORT_INDEX return zero for every port and collapsed launchd’s HASH_PORT and libdispatch’s VL_HASH to linear scans. port_obj.c and mach_right.c inherit the same assumption and are equally unportable, as is mach_traps.s (XNU trap numbers in assembly, versus dynamic sysctl mach.syscall.<name> resolution here).

What stays ours permanently

The honest counterweight: the two files that actually cost maintenance are the two with no upstream. dispatch_kevent.c (1,069 lines) has no Apple counterpart whatsoever — XNU implements EVFILT_MACHPORT in the kernel, so there is nothing in libsyscall to port. And the trap and FreeBSD-glue halves of mach_traps.c (~1,130 lines: sysctl-backed host_info, reboot(2), POSIX sem_t, mmap) are FreeBSD-shaped by construction. Adopting everything Apple offers leaves both untouched.

So the realistic outcome is ~16 files vendored-and-tracked — 12 of them net-new capability rather than replacements — ~25 headers gaining an upstream to diff against, and ~1,400 lines staying hand-maintained forever. Hand-written line count barely drops. The win is new capability at zero authorship cost, not deletion.

Not verified. The osfmk/mach directory listing came back truncated, so vm_param.h, thread_status.h, vm_map.h and vm_statistics.h are unconfirmed rather than known-absent. And the judgement that mach_msg_destroy / mach_msg_server are portable-with-voucher-paths-stripped is inferred from their stated dependencies, not verified line by line.

So “revert to open-source components instead of reimplementations” maps most cleanly onto libxpc: XPC did not exist before 10.7, so pre-10.7 launchd, syslogd, notifyd and ASL are genuine Apple open-source releases that never referenced it.

3. Where dispatch and XPC are used today

Correction. An earlier version of this page gave file counts from a plain grep for dispatch_ / xpc_. Those counts were mostly false positives — the grep matched prose in comments, and these files are heavily commented. The numbers below are after stripping comments and string literals. They are much smaller, and they change the conclusion in this section.

Verified for the configd / networking / libmach group (survey, 2026-09-01):

ComponentRaw grepReal dispatchReal xpcVerdict
libmach900Already clean
configd100Already clean
bootstrap / bootstrap_cmds000Already clean
kext_tools000Already clean
IPConfiguration13 call sites, 1 file0Trivially cleanable
hostnamed41 live file0Trivially cleanable
libSystemConfiguration93 impl + 3 public headers0Needs work — API surface

This entire group uses no XPC at all. The only xpc_ hits are two comment lines in libmach/include/mach/message.h. Counts for launchd, syslog, Libnotify and CoreFoundation are still the raw grep figures and should be assumed inflated until their surveys land.

libmach bridges for libdispatch, not on it

dispatch_kevent.c has 17 mentions of dispatch_, all in comments; its only non-comment reference includes its own header. It implements kevent_qos() and struct kevent_qos_s so unmodified Apple libdispatch compiles against Apple-shaped APIs. Its Makefile links execinfo and nothing else.

The dependency runs one way: libdispatch needs libmach, not the reverse. Removing libdispatch would leave this file unused, not broken.

The binding constraint is public API, not implementation

SCDynamicStore.h:249         SCDynamicStoreSetDispatchQueue
SCPreferences.h:246          SCPreferencesSetDispatchQueue
SCNetworkReachability.h:140  SCNetworkReachabilitySetDispatchQueue

Three installed public headers declare dispatch_queue_t parameters. These are Apple public API; removing them breaks source compatibility with unmodified Apple code, which is the project’s reason for existing. A minimal dispatch_queue_t shim would be needed even with libdispatch gone. The heaviest implementation site is SCNotify.c:345-408, a DISPATCH_SOURCE_TYPE_MACH_RECV source — the one place in this group that consumes libmach’s EVFILT_MACHPORT bridge.

3b. For much of the tree, “revert to 10.6” is a category error

The dispatch dependency in the IOKit/driver group was added by NextBSD, not inherited from Apple. Commit 05a674f“SCNotify + IOKitNotify: replace pthread+mach_msg loops with MACH_RECV dispatch sources” — moved these facades onto libdispatch from working pthread + mach_msg loops. The pre-dispatch implementation is in git history.

So the lever here is git revert-shaped, not version-shaped. Reverting Apple vendor tags would not help: four of these six components contain zero Apple source.

Verified survey of the IOKit / driver group (2026-09-01):

ComponentReal dispatchReal xpcUpstreamVerdict
libIOKit1 shipped file + public ABI0NextBSD originalNeeds work — prior pthread impl in git
kext_tools00Apple IOKitUser-907.100.13 (10.8.5)Already clean
mach_kmod0 (all comments)0 (comment)NextBSD original, tests onlyAlready clean
WLAN0 (all comments)0NextBSD originalAlready clean
mDNSResponder1 file0 (dead macro)Apple mDNSResponder-1661.xTrivially cleanable
DiskArbitration1 file, 3 call sites0NextBSD originalTrivially cleanable

Zero real XPC API calls across all six. The -lxpc on libIOKit is a false positive: IOKitMatching.c uses nvlist_* to decode the ioregistry property bag, and that lives in src/libxpc/subr_nvlist.c — a vendored fork of FreeBSD’s own libnv. Dropping libxpc means repointing at base -lnv.

The entire libdispatch surface for this group is two files: libIOKit/IOKitNotify.c and mDNSResponder/mDNSPosix/mDNSConfigStore.c. Everything else is comments, __APPLE__-guarded dead code, unshipped test binaries, or transitive linkage through libIOKit’s public header.

Boot-path risk in this group is materially lower than the syslogd precedent: four components are RunAtLoad, but none of the dispatch calls on those paths is a synchronous IPC or blocking wait — they are queue and source construction. mDNSResponder’s is additionally non-fatal, with a routing-socket fallback.

3c. launchd needs deletion, not reverting

Verified: this tree carries launchd-842.92.1 (OS X 10.9, the last open-source launchd) — provenance headers in all three Makefiles, plus 842-only content (jobmgr_new_xpc_singleton_domain, jetsam bands, waiting4attach). Not 258.

launchd can be made both dispatch-free and xpc-free without reverting to 258. Its XPC surface is a bolt-on that is dead or near-dead here: the xpc_domain half has zero callers and a stubbed demux, xpc_process is iOS jetsam, xpc_event has no clients, and job_import_defaults sits behind TARGET_OS_EMBEDDED == 0. Bootstrap, job management, MachServices and kqueue job callbacks are all the pre-XPC MIG path and are untouched.

Reverting to 258 would cost 842’s launchctl behaviour, the domain/session job-manager model recent commits have been fixing, and the os_assumes hardening — a large regression for no gain.

Verified against upstream: launchd-258.25 has no dispatch_ and no xpc_ anywhere — its loop is a direct mach_msg on a port set, and Mach readiness arrived through case EVFILT_MACHPORT in kqueue. So 10.6 launchd was self-contained on kqueue + Mach, even though libdispatch shipped in 10.6.

A stale premise worth knowing about

runtime.c:203-226 explains that demand_port_set was moved to a dispatch MACH_RECV source because FreeBSD’s filter table is sized EVFILT_SYSCOUNT == 13, so slot −14 could not be registered without a kernel patch. That is no longer true. The native filter exists at −16 (libmach/dispatch_kevent.c:116, registered by mach.ko), and mach_kmod/tests/test_evfilt_machport.c proves it works from a plain kqueue() on a port set — exactly launchd’s callback shape. The change reduces to a shim constant and Apple’s original one-line kevent_mod.

Sizing: ~1,500–1,800 lines deleted, ~300 rewritten; -lxpc, -lsystem_dispatch and -fblocks all drop out of the Makefile. Two changes carry real “does PID 1 still boot” risk and must be validated on hardware rather than CI: the demand_port_set re-registration, and replacing launchd_runtime2’s xpc_pipe_try_receive loop with the 258-style mach_msg loop.

But this does not free libxpc on its own

Nothing in the tree depends on launchd’s XPC surface — every daemon reaches launchd through MIG bootstrap. However, several daemons depend on libxpc’s object model and connection layer (xpc_connection_*, xpc_dictionary_*); syslog/aslmanager.tproj/aslmanager.c:374-405 is a live example. Dropping libxpc wholesale still breaks those consumers, so launchd is necessary but not sufficient.

4. CoreFoundation is not the anchor — correction

An earlier version of this page said CoreFoundation was the blocker for dropping libdispatch, on the strength of four _4CF SPI declarations in CFRunLoop.c. That was wrong. Those are declarations; three are compiled out on this target and the fourth is never called.

SPIStatus on this build
_dispatch_runloop_root_queue_create_4CFCompiled out — inside USE_DISPATCH_SOURCE_FOR_TIMERS, which is 0
_dispatch_runloop_root_queue_get_port_4CFCompiled out — same guard
_dispatch_runloop_root_queue_perform_4CFCompiled out — same guard
_dispatch_source_set_runloop_timer_4CFDeclared, never called anywhere

On the TARGET_OS_BSD path the run loop is already dispatch-free and Mach-free: __CFPortSet is { int kq; } — a kqueue — ports are packed pipe fd pairs, wake is a byte written to the pipe, and timers are EVFILT_TIMER. The mach_msg service path is TARGET_OS_MAC and dead here.

What genuinely remains in CFRunLoop.c: main-queue interop (one dispatch main-queue port inserted into the kqueue wait set, plus _dispatch_main_queue_callback_4CF), and a dispatch_source_t that is the only implementation of CFRunLoopRunInMode’s seconds argument. dispatch_once across 20 other files costs nothing — CF already ships _CF_dispatch_once (CFPlatform.c:1609) and wires it when __HAS_DISPATCH__ is 0.

The existing __HAS_DISPATCH__=0 switch is not the answer. It wraps entire filesCFRunLoop.c, CFSocket.c, most of CFStream.c. Upstream built it for WASI, where there is no run loop by design. Setting it here gives CF with no run loop, no CFSocket and no stream scheduling, which is fatal for GNUstep.

Effort: moderate — a few hundred lines, mostly deletion, with the portability scaffolding already present. The expensive question is not CF but every caller of dispatch_get_main_queue() elsewhere in the userland, because that API’s semantics are the CF/GCD bridge. If libdispatch goes, something must re-implement the main queue, and CFRunLoop is the natural home (~100 lines, given the pipe/kqueue plumbing is already there).

Two pre-existing bugs found while verifying this

Together these suggest the BSD CFRunLoop has not been exercised under real load. Both are worth fixing regardless of what happens to libdispatch.

5. What is known about 10.6-era launchd

Measured: our launchd has 8 files using dispatch and 5 using xpc, alongside 64 call sites into its own kqueue runtime. It is a hybrid.

Assumed, needs checking: launchd-258 (10.6) used its own launchd_runtime.c kqueue loop and did not use libdispatch — it could not, because libdispatch depends on launchd for bootstrap. If that holds, the dispatch and XPC call sites in our launchd are later additions with a documented pre-existing alternative already present in the tree.

5b. Correction: XPC adoption is far later than XPC’s existence

This page repeatedly said “pre-10.7 releases are XPC-free by construction” and treated 10.6 as the revert target. That is true but badly misleading. XPC shipped in 10.7; individual components adopted it years later. Resolving Apple’s own distribution-macOS manifest gives the real boundary for Libnotify:

Libnotify 133.1.1  ->  OS X 10.10      0 xpc_ lines   <- last pre-XPC
Libnotify 149      ->  OS X 10.11     13 xpc_ lines   <- first XPC

So a pre-XPC notifyd is a 10.10 release, not a 10.6 one — four major releases later than assumed. A “wholesale 10.6 migration” was never required to get an XPC-free notifyd.

Verified: the local copy is Libnotify-348.100.7 — macOS 26.4, imported 2026-05-16 (the VENDORED.md SHA is exactly that upstream tag object, and the working tree is byte-identical to a pristine archive of it). A revert would be a 215-version, twelve-year jump backwards.

Remove in place beats reverting, for Libnotify at least

Every XPC reference is peripheral and already inert: the entitlement gates route through xpc_copy_entitlement_for_token, which returns NULL unconditionally on FreeBSD, and the event-publisher API is a set of #define … (void)0 macros in libxpc/xpc/private.h, so the single handler that could reach NOTIFY_TYPE_XPC_EVENT is never even stored.

Remove in placeRevert to 133.1.1
Size~250 lines deleted~1,300 lines re-derived
#78 MIG demux fixKeptLost — patches code that does not exist in 2014
#120 collections fixKeptLost
os_map shimDeleted — it is all XPC-event codeDeleted
os_set shimRemainsDeleted
12 years of upstream fixesKeptDiscarded, unaudited

The public API is identical across those twelve years — 13 symbols, zero delta — and notify_register_plain (syslogd’s heaviest consumer, 13 call sites) exists in both. So the revert is possible; it is simply strictly worse. If the os_set shim is itself the burden, 133.1.1’s self-contained table.c can be lifted on its own — a much smaller change than reverting the component.

The mixed-vintage risk points at ASL, not launchd. The syslog tree was vendored the same day as Libnotify and is its deliberate macOS 26-era counterpart. Pairing a 2026 syslogd with a 2014 notifyd would invert the one vintage pairing in this tree that is currently correct — and syslogd is libnotify’s heaviest consumer, so a mismatch would most likely show up as a silent performance cliff rather than a build break.

6. Two plans, not one

The two libraries are independent axes with different sizes, different risks and different payoffs. Splitting them means the cheap, zero-risk half can land without waiting on the expensive half.

Plan A — libxpcPlan B — libdispatch
SizeSmall; mostly deletionLarge; real rewrites
Blockerlaunchd (PID 1’s event loop)The main-queue semantic
Reliability payoffRemoves the 30s boot-stall classRemoves the crash class and re-enables four disabled features
Can land in piecesYes — 3 of 4 phases carry no boot riskPartly

Plan A — libxpc

TicketWorkBoot risk
#143syslog: 6 leaf XPC sites, none on the message pathNone
#144Libnotify: ~250 lines; every XPC path is already inertNone
#145Split libnv out of libxpc (4,075 misfiled lines); relink libIOKit at base -lnvNone
#146Delete xpc_connection.c once #143 and #144 landNone
#147launchd — the gate. libxpc cannot be deleted without itHigh

Plan B — libdispatch

TicketWorkNote
#148Re-implement the main queue in CFRunLoopThe one design decision; everything else is mechanical after it
#149git revert IOKitNotify to its pthread + mach_msg loopTakes DiskArbitration and kext_tools with it, free
libSystemConfiguration (3 impl files; keep the 3 public dispatch_queue_t headers)API surface, not internals
mDNSResponder, IPConfiguration, hostnamedOne file each
syslogd + notifydThe long pole; notifyd’s IPC substrate is dispatch_mach

Plan B is worth more on reliability grounds than Plan A. Every hang and SIGSEGV in the syslog stack traces to libdispatch, not XPC. Four features are currently disabled because dispatch_resume of a kevent-backed source deadlocks: klog_in, udp_in, SIGHUP config reload, and the ASL vnode delete-monitor. Removing libdispatch lets them come back.

On reverting components: use old releases as reference, not replacement

Three independent surveys reached the same conclusion for three different components. Reverting is available and is worse in every case:

ComponentLast pre-XPC releaseRevert verdict
syslog148.8 (10.8.5)Impossible in practice — libsystem_asl.tproj and XPC arrived in the same release (217.1.4). Before it the client library lived in Apple’s Libc, which NextBSD does not vendor, so syslogd would not link.
Libnotify133.1.1 (10.10)~1,300 lines re-derived to save ~250 deleted; loses the #78 MIG demux and #120 collections fixes.
launchd392.39 (10.7.5)Use 392.39’s launchd_runtime2() as donor code for the main loop; delete the XPC slab from 842 in place.

Note the pattern that corrects this page’s original premise: components adopted XPC years after XPC shipped in 10.7 — syslog in 10.9, Libnotify in 10.11. “Pre-XPC” never meant 10.6.

Correction. An earlier version of this page said a launchd revert “fails on plist schema” because EnableTransactions and POSIXSpawnType would be silently dropped. Verified, that was overstated on every point: unknown keys log a warning and continue in all releases; EnablePressuredExit and PublishesEvents are already silently ignored by the local 842 tree because closed-source libxpc handles them; POSIXSpawnType is inert today since posix_spawnattr_setprocesstype_np is a NextBSD no-op shim; and KeepAlive — the key syslogd’s startup actually depends on — is byte-identical across 329, 392 and 842.

The real regression runs the other way: 329 and 392 implement EnableTransactions through a vm_allocate’d shared-page protocol, which a revert would reintroduce as a new porting liability, for a feature 842 provides as a free boolean.

The launchd revert is still the wrong choice, but for a plainer reason: ~860 lines of vendored-file edits would have to be re-found by hand in a file that diverges by 9,540 lines (392.39) or 12,200 (329.3.3), plus the shim set and three Makefiles for a different layout. The three NextBSD-original files — launchd_plist_scan.c, launchd_early.c, forward_stubs.c — do port cleanly, as does the MACH_PORT_INDEX fix.

Use 392.39’s launchd_runtime2() as donor code for the main-loop rewrite (~150 lines of proven Apple mach_msg-on-a-port-set), and delete the XPC slab from 842 in place: core.c:10367-11717 plus two smaller blocks, ~1,475 lines contiguous.

6b. XPC adoption timeline — the premise this page had wrong

XPC shipped in 10.7. Components adopted it years later, and several never did. Resolved against Apple’s own distribution-macOS manifest, not inferred from version numbers:

ComponentLast pre-XPCFirst XPCLocal vintageReal XPC today
syslog / ASL148.8 (10.8.5)217.1.4 (10.9)406 (macOS 26.4)136 refs, 6 leaf files
Libnotify133.1.1 (10.10)149 (10.11)348.100.7 (macOS 26.4)~10, all inert
launchd392.39 (10.7.5)
last that does not link libxpc
442.21 (10.8)
first libxpc main loop
842.92.1 (10.9.5)Load-bearing — PID 1’s loop
mDNSResponderXPC confined to mDNSMacOSX/, never vendored2881.0.25 (macOS 26.0)0 — macro with no callers
configdNever adopted XPC — still MIG at macOS 26NextBSD-original0
libSystemConfigurationn/aNextBSD-original0
DiskArbitrationClient framework never adopted XPCNextBSD-original0

Apple never moved SCDynamicStore off MIG. Verified at configd-1405.120.5 (macOS 26): config.defs still declares subsystem config 20000, all 18 MIG server implementations are XPC-free, and SCDOpen.c still does bootstrap_look_up2. Apple used XPC for the bulk read-only blobs layered around configd — reachability (10.8), dnsinfo/nwi (10.9), IPMonitor (10.12) — never for the store itself. NextBSD’s design matches Apple’s current one.

Naming collision worth knowing. NextBSD’s libSystemConfiguration is a port of Apple’s SystemConfiguration.fproj (the framework). Apple also ships configd/libSystemConfiguration/ — an entirely different XPC-native helper for dnsinfo and nwi blobs, which has no pre-XPC release, having been XPC-native from birth (configd-596.12, 10.9). Reasoning about “libSystemConfiguration’s XPC history” from the upstream directory name reaches the opposite of the correct conclusion.

Methodology warning. git grep -E '\bxpc_' silently returns zero at every tag — git’s POSIX ERE has no \b. Two surveys hit this independently. Any count of “zero XPC references” produced that way is a false negative. The zeroes recorded on this page were established by reading preprocessor conditions and Makefiles rather than by grep alone, but spot-check before treating any zero as licence to delete.

6c. The vintage map, and where it actually bites

This tree spans roughly 17 years. That is worth knowing before either plan starts moving components around.

Component / partUpstreamReleaseEra
kext_tools/compat/xnu headersunpinned~2008
kext_tools/kext.subprojIOKitUser907.100.13OS X 10.9
libmach/*.defsxnu osfmk/mach/closest 3247.1.106~10.9–10.11
bootstrap_cmds (mig)bootstrap_cmds136 ≡ 138macOS 15 / 26
hostnamed/vendored/set-hostname.cconfigd IPMonitor1345–1405.120.5macOS 15 / 26
libCoreFoundationswift-corelibs-foundationSHA 0e20e4a7May 2026
libIOKit, libmach body, mach_kmod, WLAN, IPConfiguration, hostnamed body, DiskArbitration, configd, libSystemConfigurationNextBSD-original2025–26

Mixed vintage has already caused one real failure, and it is the archetype. macOS 15/26-era mig generates stubs emitting MACH_RCV_TRAILER_AV, which this tree’s ~10.9-era libmach/include/mach/message.h does not define. Every bootstrap_look_up* returned MIG_TRAILER_ERROR (-309). Fixed in 5ebbe3d by patching user.c to emit MACH_RCV_TRAILER_AUDIT instead.

A 2024 generator against a 2013 header set. That is the shape to watch for — not ABI breakage between daemons, but a modern tool emitting something an older header has never heard of.

Six of the nine components surveyed here are NextBSD-original, which closes the revert question for them outright: there is no older Apple version to go back to. And across all nine there are zero xpc_* API calls — the only libxpc coupling is libIOKit’s -lxpc, which is for libnv. Dropping libxpc in this group means re-homing libnv, not de-XPC-ing anything.

Documentation drift found while establishing this — six items, filed as #154: PROVENANCE.md dates IOKitUser-907.100.13 to 10.8.5 when it is 10.9 (and 10.9 is the last pre-XPC IOKitUser, so the choice was better than documented); NOTICE.md lists wrong upstreams for libIOKit, libmach and IPConfiguration and omits WLAN, mach_kmod and bootstrap_cmds entirely.

7. Do this before either plan

  1. Make the boot test gate (#87). CI has passed a kernel that bricked the box, and a userland PR while SYSLOG-RUN failed on both arches under boot_soft. Both plans’ risky phases are PID-1-adjacent.
  2. Get a serial console on the VM. A PID 1 failure currently means physical LiveCD recovery with no console output.

8. Pairing with epic #145

A separate survey recommends shelving #145 as a bug fix: both its rationales were retracted, and its own acceptance criterion — a clean cold-boot wedge rate — was met by the userland EV_CLEAR fix at 22/80 → 80/80 without any of the work.

“10.6-era XNU Apple shape” is a design goal, not a bug fix, and that argument survives. Apple’s name encoding (index << 8 | generation) is what XNU does; fd-backed names with MACH_PORT_GEN() hardcoded to 0 are the deviation. If the objective is to match XNU, #145 is coherent on its own terms — it just should not be justified by the wedge any more.

Four defects in the current fd-backed design are worth fixing either way, all verified by reading:

  1. In no ticket: ipc_entry_lookup does entry = fp->f_data; fdrop(fp); return entry; — returning a pointer after releasing the reference that keeps it alive. All 17 call sites use it afterwards.
  2. #150: mach_port_deallocate needs two calls per send right, leaking entries, ports and fds in long-lived daemons.
  3. Security shape, from #152: any ordinary fd passed as a port name is wrapped into a receiverless port and the message is swallowed. fileport_makeport is an ENOSYS stub, so no legitimate user exists.
  4. #153, concrete half: fork() leaves parent and child sharing ipc_entry structs across two spaces.

And a correction worth keeping on the record: the ten kernel counters could not have excluded name recycling, because they counted failures. A stale name resolving to a different live port is a successful lookup returning the wrong object.