FreeBSD ASL + libnotify — porting plan

Port Apple's ASL (Apple System Log) + libnotify+notifyd as a single cohesive block, replacing the removed FreeBSD-syslogd and unblocking the downstream Apple-service daemon ports (configd, IPConfiguration, PowerManagement, DiskArbitration, IOKitUser). Status: draft; synthesized from five parallel research agents on 2026-05-16. Supersedes the earlier v0 plan (which assumed AF_UNIX / GNUstep Distributed Objects substrate — wrong now that Mach IPC + libxpc + libdispatch + libCoreFoundation are all working).

TL;DR

Contents

  1. Motivation — current state + why ASL is the path of least resistance
  2. Scope of port
  3. Apple ASL source layout
  4. syslogd architecture (input modules, output modules, ASL DB)
  5. Why libnotify+notifyd ship alongside ASL
  6. FreeBSD syslog(3) interop strategy
  7. 6.5 Would switching to Apple utilities drop the bsd_in.c workaround?
  8. Install layout
  9. Stubs needed
  10. Consumer guide — how to use ASL from C / Objective-C
  11. Phased delivery (J1-J4)
  12. CI smoke markers
  13. launchd log.c interaction
  14. Open questions
  15. Source citations

1. Motivation

The runtime ISO currently ships no syslog daemon at all. FreeBSD-syslogd was removed at commit 88694f0 (2026-05-16) alongside FreeBSD-rc, FreeBSD-devd, and several other base packages we no longer need under PID-1 launchd. Apps that call syslog(3) via libc (getty, login, and a small handful of FreeBSD-base utilities) write to /var/run/log — which doesn't exist — and the messages silently drop. Kernel printfs accumulate in the kernel msgbuf and never make it to any persistent log.

This is fine for boot-to-login validation (no boot-critical decision depends on logs), but blocks essentially every next step. hwregd needs to log events for operational visibility. configd, IPConfiguration, mDNSResponder, PowerManagement — every Apple-source service daemon — calls asl_log() natively in its source. We can either:

  1. Port Apple's ASL stack as the structured replacement, which is what every Apple-source daemon already writes for, OR
  2. Reinstall FreeBSD-syslogd, which works for libc syslog(3) consumers but leaves the Apple-source daemons either calling no-op stubs or shelling out — a permanent two-track logging architecture.

Option 2 is the wrong end state; Apple's daemons assume ASL and we'd be undoing this later anyway. Option 1 — port ASL once, correctly — is the path the project's scope_split memory already commits to ("Apple source only for system services: Mach/launchd/configd/notifyd/asl/libdispatch/libxpc/liblaunch"). ASL is in scope.

2. Scope of port

ComponentSource locationPhaseNotes
libsystem_asl (client lib)syslog/libsystem_asl.tproj/J1~14.6k LoC. Public asl.h, internal subprojects. Every Apple daemon links this.
aslcommon (static lib shared by daemon + client)syslog/aslcommon/J1~4.5k LoC. asl_ipc.defs MIG (subsystem 114), asl_memory.c, asl_common.c.
syslogd daemonsyslog/syslogd.tproj/J2-J3~8.4k LoC. Mach RX loop + input/output modules + ASL store driver.
New: bsd_in.c moduleNEW under syslogd.tproj/J3~150 LoC. Listens on /var/run/log (AF_UNIX SOCK_DGRAM) so FreeBSD libc syslog(3) consumers reach ASL transparently.
RFC 5424 branch in asl_syslog_input_convert()syslogd.tproj/daemon.c:1057J3~50 LoC. FreeBSD libc emits RFC 5424 (<pri>1 timestamp ...); Apple parses only RFC 3164.
libnotify client libLibnotify/ (top-level + headers)J1~7.7k LoC. notify_register_*, notify_post, notify_check, etc.
notifyd daemonLibnotify/notifyd/J2~5.1k LoC. Mach-backed pub/sub server. Required because configd/pmconfigd hard-depend on real notify behavior.
os/* header stubsNEW under freebsd-shims/J1~700 lines. os_log/os_assumes/os_activity/os_transaction_private macro to printf/syslog/no-op. Required to compile.
membership.h stubNEW under freebsd-shims/J1~20 lines. mbr_uid_to_uuid → KERN_FAILURE. ASL store ACL groups degrade to uid/gid.
configuration_profile.h stubNEW under freebsd-shims/J1~10 lines. MDM profile overrides — irrelevant on FreeBSD; returns NULL.
aslmanager log-rotation daemonsyslog/aslmanager.tproj/J4~2k LoC. Without it /var/log/asl/ grows unbounded (syslogd self-rotates current files but never archives or deletes). Real disk-fillup consequence — not optional for a shipping system.
syslog(1) CLIsyslog/util.tproj/J5~2.9k LoC. Required for structured queries against the binary /var/log/asl/*.asl database (operators have no shell-side ASL read access without it). Send-side covered by FreeBSD's existing logger(1) + our bsd_in.c, but read-side is syslog(1)-only.
newsyslogsyslog/newsyslog/SKIPFreeBSD base already has working newsyslog; no need to port Apple's variant.

3. Apple ASL source layout

Vendored locally at /Users/jmaloney/Documents/launchd/syslog/. License: APSL 2.0. ~32k LoC total C.

Six Xcode targets in syslog.xcodeproj/project.pbxproj:

aslcommon          (static lib)   libaslcommon.a
  └ asl_ipc.defs (MIG subsystem 114), asl_memory.c, asl_common.c

libsystem_asl      (shared lib)   libsystem_asl.dylib
  └ asl.c (2064), asl_msg.c (3295), asl_file.c (2653),
    asl_core.c (1164), asl_store.c (1155), asl_legacy1.c (807),
    asl_string.c (747), asl_client.c (614), asl_msg_list.c (587),
    asl_object.c (426), asl_util.c (342), asl_fd.c (328),
    syslog.c (288 — BSD syslog(3) shim that wraps to ASL),
    asl_mt_shim.c (100)

syslogd            (tool)         /usr/sbin/syslogd
  └ syslogd.c (712), daemon.c (1426), dbserver.c (1648),
    asl_action.c (2457), bsd_out.c (811), klog_in.c (141),
    udp_in.c (222), remote.c (935)
    links: libaslcommon.a, libbsm.dylib

aslmanager         (tool)         /usr/libexec/aslmanager
util               (tool)         /usr/bin/syslog
newsyslog          (tool)         skip — FreeBSD base has its own

Dependency DAG: aslcommon → libsystem_asl → {syslogd, aslmanager, util}. newsyslog is standalone. No Security.framework, IOKit, CoreFoundation, or libsystem_info usage anywhere (verified grep).

4. syslogd architecture

Input modules (registered at syslogd.c:224-229)

ModuleSourceWireStatus on FreeBSD
m_aslMach IPC via com.apple.system.logger (launchd MachServices port)MIG _asl_server_messageWorks as-is once liblaunch returns the Mach port
m_klog_inklog_in.c reads /dev/klog via dispatch READ sourcePlain open(O_RDONLY|O_NONBLOCK)Works on FreeBSD unmodified — /dev/klog exists, same single-reader semantic
m_udp_inudp_in.c, network RFC 3164 from UDP 514SOCK_DGRAM from launchd SocketsDisabled by default; enable per-plist if remote syslog needed
m_remoteremote.c, AF_UNIX SOCK_STREAM control socketFor syslog -w watchersiOS-targeted; skippable for v1
m_bsd_in (NEW)NEW bsd_in.c modeled on klog_in.cAF_UNIX SOCK_DGRAM on /var/run/logRequired for FreeBSD getty/login interop

Output modules (in-process, linked at build time)

5. Why libnotify+notifyd ship alongside ASL

The agent audit found 347 notify_* call sites across 6 downstream Apple daemons:

Daemonnotify_* callsFilesBehavior if stubbed
ASL (syslogd + libsystem_asl + aslmanager + util)529Loses syslog -w, syslog -c, asl.conf =notify. Logging itself fine.
IOKitUser (pwr_mgt + ps subprojects)5611IOPMAssertion / IOPS notifications broken.
configd6014Permanently broken. Event-driven; network-change/host-name-change/ip-plugin-recompute all hang on notify wakeups.
IPConfiguration71Lease renewal + interface state-change dropped.
PowerManagement (pmconfigd)15417Permanently broken. Sleep/wake/lid/battery-thresh all hang.
DiskArbitration62Disk arrival/eject callbacks broken.
Total33554

For ASL alone, stubbing is plausible (~80 lines across 9 files). But the moment configd or pmconfigd enters scope — and both are in our commit-to-port list — the stub story collapses. libnotify itself has only 3 unported deps, all already on our roadmap. Cost: ~12.8k LoC, ~1.5-2 weeks port effort. Versus stubbing across 6 daemons: ~480 lines of degraded-mode shim, configd + pmconfigd unusable. The leverage ratio (one ~12.8k-LoC port unblocks 5 daemon ports cleanly) makes libnotify the highest-priority cross-cutting dep.

6. FreeBSD syslog(3) interop strategy

Today's broken chain:

FreeBSD getty/login
  → libc syslog(3) (lib/libc/gen/syslog.c)
  → connect("/var/run/log", AF_UNIX, SOCK_DGRAM)
  → ECONNREFUSED (no listener) → silent drop

Target chain:

FreeBSD getty/login
  → libc syslog(3)
  → sendto("/var/run/log", AF_UNIX, SOCK_DGRAM, RFC-5424-msg)
  → Apple syslogd's NEW bsd_in.c module
  → asl_input_parse() with patched RFC 5424 branch
  → ASL store + /etc/syslog.conf routing to /var/log/messages

Three concrete patches required (all in syslogd.tproj/):

  1. New bsd_in.c modeled on klog_in.c. Get the listening fd from launchd via LAUNCH_JOBKEY_SOCKETS. Per-dgram recv()asl_input_parse(buf, len, NULL, SOURCE_BSD_SOCKET). Register m_bsd_in in syslogd.c:215-229.
  2. RFC 5424 branch in asl_syslog_input_convert() (daemon.c:1057). FreeBSD libc emits <pri>1 YYYY-MM-DDTHH:MM:SS ...; Apple's existing parser hard-checks for the RFC 3164 timestamp Mmm dd HH:MM:SS. Add a branch: if next bytes after <pri> are 1 , parse RFC 5424.
  3. launchd Sockets entry in the syslogd plist:
    <key>Sockets</key>
    <dict>
      <key>BSDSystemLogger</key>
      <dict>
        <key>SockPathName</key><string>/var/run/log</string>
        <key>SockType</key><string>dgram</string>
        <key>SockPathMode</key><integer>0666</integer>
      </dict>
    </dict>

Kernel log ingest works as-is: klog_in.c opens /dev/klog via plain open(O_RDONLY|O_NONBLOCK), FreeBSD's /dev/klog from sys/kern/subr_log.c has the same single-reader semantic. No patches needed. /etc/syslog.conf compat is already in Apple syslogd via bsd_out_init() at bsd_out.c:732 — also no patches.

6.5 Would switching to Apple's utilities drop the bsd_in.c workaround?

Short answer: partially, and not worth chasing. Below is the explicit matrix.

Two paths a userland program can reach ASL:

  1. Apple-utility path: program links libsystem_asl.so, calls asl_log() directly OR calls syslog(3) which is intercepted by the libsystem_asl shim (libsystem_asl.tproj/src/syslog.c, 288 lines) and routed to syslogd over Mach IPC. No bsd_in.c involved.
  2. FreeBSD-utility path: program links FreeBSD libc only, calls syslog(3) which writes RFC 5424 to /var/run/log AF_UNIX SOCK_DGRAM. bsd_in.c required to listen on that socket and route into ASL.

So bsd_in.c is the catch-all for path 2. Porting an Apple utility moves that one binary to path 1, but bsd_in.c stays mandatory for every other FreeBSD-base utility still on path 2.

Per-utility matrix

FreeBSD-base utility In our rootfs? Apple equivalent Apple version uses asl directly? Porting drops bsd_in.c for this consumer? Worth porting?
/usr/libexec/getty Yes (FreeBSD-runtime) system_cmds/getty.tproj/ Yes (via libsystem_asl syslog shim + native asl calls) Yes (for getty) No — Apple version has Darwin audit hooks + asl-direct integration we'd strip; result functionally equivalent to FreeBSD's. bsd_in.c covers transparently.
/usr/bin/login Yes (FreeBSD-runtime) system_cmds/login.tproj/ Yes (asl + audit session) Yes (for login) No — Same logic as getty. Apple's adds OpenDirectory hooks we don't have; strip-and-port gives near-identical behavior.
/usr/bin/su Yes (FreeBSD-runtime) system_cmds/su.tproj/ Yes (asl + audit) Yes (for su) No — Same reasoning.
/usr/bin/passwd Yes (FreeBSD-runtime) system_cmds/passwd.tproj/ Yes Yes (for passwd) No — Same reasoning.
/usr/sbin/cron Optional (not in minimal rootfs) cron (Apple ships BSD cron with patches) Partial — calls syslog(3), not asl directly No real win — even Apple cron uses syslog(3) No — Both versions go through bsd_in.c anyway.
/usr/libexec/atrun Optional Same as cron — BSD inheritance No No No
/usr/sbin/sshd No (FreeBSD-ssh removed at 88694f0) Apple openssh-portable fork (heavily patched) Yes (asl + audit) Yes (for sshd when it returns) Maybe later — when sshd comes back, the Apple version brings session-context logging worth having. Independent of this plan.
/sbin/dhclient No (removed at 88694f0; will be replaced by IPConfiguration) N/A — IPConfiguration is the Apple equivalent Yes (asl direct) Yes (IPConfiguration won't use syslog(3)) Yes — separate plan
/usr/sbin/newsyslog Yes (FreeBSD-runtime) syslog/newsyslog/ (Apple variant) No (BSD-style) No No — FreeBSD's works; Apple's variant in our vendored syslog repo is intentionally skipped per the J0 plan.
/usr/sbin/syslogd No (removed at 88694f0) This plan N/A — IS the daemon N/A Yes — this plan
/usr/sbin/powerd Not in our build path PowerManagement/pmconfigd/ Yes (asl + notify) Yes (when pmconfigd lands) Eventually — covered by IOKit/PowerManagement plan downstream.
/usr/sbin/ntpd Not in our build path Apple ntp fork (deprecated on macOS in favor of timed) Some — calls asl in patches Partial No — Apple themselves abandoned ntpd; if time sync becomes a need, port Apple's timed or ship freebsd-base ntpd through bsd_in.c.
Future ports-tree apps using syslog(3) Whatever the user installs N/A — third-party No No (can't patch user-installed pkgs) bsd_in.c is the only answer

Conclusion

Even if we ported every Apple system_cmds utility that has a syslog-using counterpart (getty / login / su / passwd / atrun), bsd_in.c would still be required for:

And per the earlier audit (apple-userland-cmds plan and the IOKit-plan synthesis), Apple's userland utilities that talk to the kernel (getty/login/ifconfig/route/netstat/ping/ps/top/mount/etc.) aren't worth porting: their value over FreeBSD's is near-zero after stripping Darwin-specific kernel-ABI ties (audit sessions, asl-direct integration, OpenDirectory hooks), all of which we don't have anyway. bsd_in.c is the leverage move: ~200 LoC covers every present and future FreeBSD syslog(3) consumer transparently.

The only Apple utilities worth porting from a logging-perspective alone are those that talk to Apple services (launchctl, scutil, networksetup, defaults, log, syslog(1) itself) — they have no FreeBSD equivalent and their value compounds beyond just logging.

7. Install layout

ArtifactPath
libsystem_asl.so/usr/lib/system/libsystem_asl.so + sonname symlink
libnotify.so (client lib)/usr/lib/system/libnotify.so + sonname symlink
Public headers (asl.h, notify.h, notify_keys.h)/usr/include/
Private headers (asl_private.h, etc.)/usr/include/asl/
syslogd binary/usr/sbin/syslogd
notifyd binary/usr/sbin/notifyd
aslmanager binary (J5)/usr/libexec/aslmanager
syslog(1) CLI (J5)/usr/bin/syslog
launchd plists/System/Library/LaunchDaemons/com.apple.syslogd.plist, com.apple.notifyd.plist
Default asl.conf/etc/asl.conf + drop-in dir /etc/asl/
ASL DB/var/log/asl/*.asl
BSD text log/var/log/system.log, /var/log/messages, etc.

8. Stubs needed

9. Consumer guide — how to use ASL from C / Objective-C

This section is for application developers writing new code (or porting existing code) that wants to log via ASL after this plan ships.

9.1 C — direct ASL API

Header: <asl.h> (lives at /usr/include/asl.h after J1).
Link: -lsystem_asl (the soname is libsystem_asl.so.1 under /usr/lib/system/; rpath baked at link time per install_layout_policies).

Minimal: fire-and-forget messages

#include <asl.h>

int main(void)
{
    asl_log(NULL, NULL, ASL_LEVEL_NOTICE,
            "hello from %s pid=%d", "myapp", (int)getpid());
    return 0;
}

Build: cc -o myapp myapp.c -L/usr/lib/system -lsystem_asl (the rpath baked at install layout makes runtime resolution automatic). No asl_open needed — the implicit default client is created on first asl_log.

Recommended: per-process client with facility

#include <asl.h>

static asl_object_t client;

int main(void)
{
    /* facility = "com.example.myapp", options = 0 */
    client = asl_open("myapp", "com.example.myapp", 0);

    asl_log(client, NULL, ASL_LEVEL_NOTICE, "starting up");

    /* ... */

    asl_close(client);
    return 0;
}

Structured messages with key-value pairs

#include <asl.h>

asl_object_t msg = asl_new(ASL_TYPE_MSG);
asl_set(msg, ASL_KEY_MSG,    "request completed");
asl_set(msg, ASL_KEY_LEVEL,  ASL_STRING_NOTICE);
asl_set(msg, "Request-ID",   request_id);
asl_set(msg, "Latency-ms",   latency_str);
asl_send(client, msg);
asl_release(msg);

Querying / filtering / live-tail are covered in asl.h proper (asl_search, asl_next, asl_match); not needed for most daemons.

BSD syslog(3) still works

Existing code that calls openlog() / syslog() / closelog() needs zero changes. There are two paths and both reach ASL:

  1. If linked against libsystem_asl.so: the shim in libsystem_asl.tproj/src/syslog.c (288 lines) overrides libc's syslog(3) and routes directly to ASL via Mach IPC.
  2. If linked against FreeBSD libc only: syslog(3) writes RFC 5424 to /var/run/log AF_UNIX SOCK_DGRAM → Apple syslogd's new bsd_in.c module → ASL.

Either path lands in /var/log/asl/*.asl (binary ASL store) and /var/log/messages (per /etc/syslog.conf).

9.2 Objective-C

ASL is a pure C API. Objective-C consumers call it identically — no Foundation wrapper required:

#import <Foundation/Foundation.h>
#import <asl.h>

@interface MyLogger : NSObject
@property (assign) asl_object_t client;
@end

@implementation MyLogger

- (instancetype)init {
    if ((self = [super init])) {
        _client = asl_open([@"MyApp" UTF8String],
                           [@"com.example.myapp" UTF8String], 0);
    }
    return self;
}

- (void)logRequest:(NSString *)requestID latency:(NSTimeInterval)ms {
    asl_object_t msg = asl_new(ASL_TYPE_MSG);
    asl_set(msg, ASL_KEY_MSG, "request completed");
    asl_set(msg, ASL_KEY_LEVEL, ASL_STRING_NOTICE);
    asl_set(msg, "Request-ID", [requestID UTF8String]);
    asl_set(msg, "Latency-ms",
        [[NSString stringWithFormat:@"%.0f", ms] UTF8String]);
    asl_send(_client, msg);
    asl_release(msg);
}

- (void)dealloc {
    asl_close(_client);
}

@end

Build with the standard GNUstep / libobjc2 chain: cc -fobjc-runtime=gnustep-2.0 myapp.m -lsystem_asl -lobjc -lgnustep-base -o myapp. The libobjc2 runtime is unchanged by ASL — there is no ObjC bridge layer; logging is a flat C call from any method.

NSLog() behavior

GNUstep's NSLog() writes to stderr by default. To have NSLog() output also land in ASL, install an NSLog handler that wraps asl_log:

static void asl_nslog_handler(NSString *fmt, va_list args) {
    NSString *s = [[NSString alloc] initWithFormat:fmt arguments:args];
    asl_log(NULL, NULL, ASL_LEVEL_NOTICE, "%s", [s UTF8String]);
}

/* In main, before any NSLog: */
_NSSetLogCStringFunction(asl_nslog_handler); /* GNUstep extension */

Alternative for new ObjC code: skip NSLog() entirely; call asl_log() directly — same as the C example above.

9.3 What about os_log()?

Apple's newer os_log() family (introduced on macOS 10.12 / iOS 10) is part of the closed-source libsystem_trace + kernel firehose. We're not porting that. Our os/log.h stub (Phase J1) provides macro definitions that expand to asl_log(), so any Apple source code that calls os_log() compiles and routes to ASL transparently — but new application code should call asl_log() directly for clarity. os_log() is preserved for source-compat with the Apple daemons we vendor; it's not the recommended consumer API for new C/ObjC code on this stack.

9.4 Levels (priority constants)

ASL constantsyslog(3) equivNumericMeaning
ASL_LEVEL_EMERGLOG_EMERG0System unusable
ASL_LEVEL_ALERTLOG_ALERT1Action required immediately
ASL_LEVEL_CRITLOG_CRIT2Critical condition
ASL_LEVEL_ERRLOG_ERR3Error
ASL_LEVEL_WARNINGLOG_WARNING4Warning
ASL_LEVEL_NOTICELOG_NOTICE5Normal but significant (default for daemons)
ASL_LEVEL_INFOLOG_INFO6Informational
ASL_LEVEL_DEBUGLOG_DEBUG7Debug-level message

9.5 Querying logs at runtime

From the shell (after J5 ships syslog(1)):

# All notice+ messages
syslog -k Level Nle 5

# Messages from a specific sender
syslog -k Sender myapp

# Live tail
syslog -w 0

# Standard BSD text log also works
tail -f /var/log/messages

10. Phased delivery

J0 — Repository setup ~1-2 days

Vendor syslog/ and Libnotify/ at src/syslog/ and src/Libnotify/ in the freebsd-launchd-mach repo. Initial commit of unmodified source as the diff baseline.

J1 — libsystem_asl + libnotify (compile-only) ~1-1.5 wk

Build libsystem_asl.so + libnotify.so via bsd.lib.mk style Makefiles (mirroring libxpc/libdispatch). Wire MIG asl_ipc.defs and notify_ipc.defs via in-tree migcom. Install to /usr/lib/system/. Drop the os/* + membership.h + configuration_profile.h stubs. Suppress __API_DEPRECATED noise.

Smoke marker: ASL-LIB-OK — hello binary calls asl_log(NULL, NULL, 6, "hi"), links + runs.

J2 — syslogd + notifyd Mach RX ~1.5-2 wk

Build syslogd with klog_in/udp_in/remote modules disabled at init_modules() — Mach-only ingest via database_server() loop. Build notifyd similarly: minimal Mach-port server registering com.apple.system.notification_center. Both run under PID-1 launchd via plists.

Smoke markers: ASL-RUN-OK, NOTIFYD-RUN-OK, ASL-STORE-OK (test asl_log → persisted to /var/log/asl/), NOTIFY-ROUNDTRIP-OK.

J3 — bsd_in.c + /dev/klog + /etc/syslog.conf ~1 wk

Implement the three FreeBSD-interop patches: new bsd_in.c input module, RFC 5424 branch in asl_syslog_input_convert(), BSD-socket Sockets entry in plist. Enable klog_in + bsd_out modules. Ship minimal /etc/syslog.conf.

Smoke markers: SYSLOG-RESTORED-OK, KLOG-OK.

J4 — aslmanager + validation + smoke + hwregd handoff ~2-3 wk

Build aslmanager daemon (~2k LoC). Install at /usr/libexec/aslmanager. Wire launchd plist for periodic activation (Apple ships it as on-demand via XPC from syslogd; we can match). Without aslmanager /var/log/asl/ grows unbounded — real disk-fillup consequence, not optional.

Full-boot CI assertion: PID-1 launchd boots; getty + hwregd + syslogd + notifyd + aslmanager all up; login: prompt appears; tail /var/log/messages shows getty/login session creation. Update hwregd's xlog() to call asl_log() instead of fprintf(stderr, ...) — single ~5-line additive patch, no plist change needed.

Smoke markers: ASLMANAGER-OK, ASL-FROM-HWREGD-OK, BOOT-WITH-ASL-OK.

J5 — syslog(1) CLI ~1-2 wk

Final phase of the ASL port. ~2.9k LoC operator CLI. Required for structured queries against the binary /var/log/asl/*.asl database — without it operators have no shell-side ASL read access (only tail /var/log/messages via the BSD-text output route, which flattens structured fields). Also handles runtime daemon filter control (syslog -c <pid> <mask>) and live-tail with structured filtering (syslog -w 0 -k Sender hwregd).

Send-side from shell is already covered by FreeBSD's existing logger(1) + our bsd_in.c socket compat, so syslog(1)'s send mode is redundant but harmless.

Smoke marker: SYSLOG-CLI-OK — query roundtrip writes a message via libsystem_asl then reads it back via syslog -k Sender hwregd.

11. CI smoke markers (full set)

MarkerPhaseWhat it asserts
ASL-BUILD-OKJ1libsystem_asl.so + libnotify.so + syslogd + notifyd binaries built + installed
ASL-LIB-OKJ1Hello binary links + runs against libsystem_asl + libnotify
ASL-RUN-OKJ2syslogd starts under PID-1 launchd, registers Mach service
NOTIFYD-RUN-OKJ2notifyd starts under PID-1 launchd, registers Mach service
ASL-STORE-OKJ2Test client asl_log() → persists to /var/log/asl/
NOTIFY-ROUNDTRIP-OKJ2Client A posts; client B (registered watcher) receives via Mach
SYSLOG-RESTORED-OKJ3FreeBSD-base syslog(3) caller lands in ASL + /var/log/messages
KLOG-OKJ3Kernel printf appears in ASL
ASL-FROM-HWREGD-OKJ4hwregd's asl_log() calls appear in /var/log/asl/
BOOT-WITH-ASL-OKJ4Full PID-1 boot to login with all daemons running
SYSLOG-CLI-OKJ5syslog(1) query roundtrip: write via libsystem_asl, read back via syslog -k Sender hwregd

12. launchd log.c interaction

launchd-842 has its own log subsystem in src/launchd/src/log.c using launchd_syslog(). On macOS the internal backend writes to ASL via asl_log_message(); in our port the backend currently writes to /dev/console.

Decision: keep the BSD path for now. Once ASL is up, optionally swap launchd's internal backend to call asl_log_message() — behind-the-scenes change in log.c, no callsite churn across launchd's 28k LoC. The BSD-only backend stays useful as a fallback during early boot before syslogd is reachable. Additive, not undoing.

13. Open questions

  1. asl.conf merge policy (RESOLVED): ship a custom ~10-line FreeBSD-specific config. Neither Apple variant is right — osx variant has dead rules for services we don't have (loginwindow, WindowServer, CrashReporter), ios variant lacks the system.log BSD-text writer. Custom is simpler than either and covers exactly what we need: ASL DB destination + system.log BSD-text writer + level-notice store rule + kernel-sender rule.
  2. notifyd PID-1 ordering (RESOLVED): no explicit ordering needed — rely on launchd's MachServices port-on-behalf pattern, same as Apple does on macOS. launchd holds the receive-right for any declared MachServices name as soon as the plist loads at boot, so bootstrap_look_up() returns a usable send-right immediately, even before the target daemon runs. First message queues in the kernel Mach port, triggers on-demand activation, drains when the daemon's mach_msg_server starts. Our launchd-842 port already implements this — it's how com.apple.bootstrap_server works (proved in phase_g2_complete).
  3. fileport syscall surface (RESOLVED): notifyd + ASL need sys/fileport.h's fileport_makefd / fileport_makeport. Apple implements as kernel Mach traps (XNU), with the kernel tracking fileglob ↔ Mach-port associations and supporting atomic fd-passing inside Mach message bodies. Decision: same architecture on FreeBSD via new ops in our mach.ko trap multiplexer (slot 219). Implementation: fget(td, fd, ...) + fhold() for makeport, falloc_noinstall() + finstall() for makefd, store struct file * in the Mach port's kobject slot. ~200-300 LoC, same shape as the task_get/set_special_port ops we added in Phase G prereqs. Slotted as a J2 prereq; bumps J2 from ~1.5-2 wk to ~2.5-3 wk.
  4. Log rotation (RESOLVED): aslmanager ships alongside ASL in J4, not deferred. Without it /var/log/asl/ grows unbounded — real disk-fillup consequence, not optional for a shipping system.
  5. hwregd asl_log() swap timing: part of J4 (clean handoff in the ASL plan) or separate hwregd iter-2 commit? Leaning toward J4 for atomicity — the ASL plan ships with at least one real consumer wired up.

14. Source citations

Apple source (vendored locally)

FreeBSD source

Related project memory


Plan synthesized from five parallel research agents on 2026-05-16: (1) Apple syslog source audit, (2) libnotify dependency analysis, (3) FreeBSD syslog interop audit, (4) existing plan validation, (5) ASL + IOKit shared-dep audit. Supersedes the earlier freebsd-asl-plan.html v0 (which assumed an AF_UNIX / GNUstep Distributed Objects substrate — wrong now that Mach IPC + libxpc + libdispatch + libCoreFoundation are all working). Section 9 (Consumer guide) added per user request: documents how C and Objective-C consumers use ASL after this plan ships. Please flag anything that should change before this becomes a committed plan.