Re: rcd(8) - new service manager daemon

Baptiste Daroussin <[email protected]> Mon, 15 Jun 2026 08:47:46 +0200
Newsgroups gmane.os.freebsd.devel.hackers
Message-ID <[email protected]>
On Sun 14 Jun 16:15, Alan Somers wrote:
> On Sun, Jun 14, 2026 at 1:26 PM Baptiste Daroussin <[email protected]> wrote:
> >
> > Hello everyone
> >
> > I have been working for the past years on rcd(8), a new service manager
> > daemon for FreeBSD, some of you might recall a presentation in french 15
> > years ago or some discussion in BSDcam also that old!
> > I would like to start a discussion about its integration into the tree.
> >
> > What is rcd?
> >
> > rcd(8) is a service manager daemon called by init(8) (in place of
> > /etc/rc).  It reads service definitions from UCL unit files
> > (/etc/rcd.d/*.ucl), builds a dependency DAG, and starts services in
> > parallel.  After boot completes, it forks to background and stays
> > running as a supervision daemon (automatically restarting failed
> > services and accepting control commands via a UNIX socket).
> >
> > Key features:
> >
> >   - Parallel boot via dependency DAG (no more serial rc.d execution)
> >   - Process tracking via pdfork(2) descriptors (no PID file races)
> >   - Subreaper via procctl(2) (no orphaned process escape)
> >   - Socket activation (pre-bound sockets passed via fd inheritance)
> >   - Resource control per service via rctl(2)
> >   - Service isolation via native jail(2) integration
> >   - OOM protection via procctl(2) PROC_SPROTECT
> >   - UCL-based unit files (JSON Schema validated)
> >   - Embedded Lua interpreter for inline service hooks
> >   - Template units for per-instance services (e.g., dhclient@em0)
> >   - Safe in-place binary upgrade (SIGUSR1: save state, re-exec)
> >   - Per-service access control on the control socket
> >   - Suspend/resume support
> >
> > User interface: rcctl(8)
> >
> > Service management is done via rcctl(8):
> >
> >     rcctl start sshd
> >     rcctl enable sshd
> >     rcctl restart dhclient@em0 netif@em1
> >     rcctl status
> >     rcctl show nginx
> >
> > 100% backward compatibility
> >
> > This is a hard requirement: rcd must work on existing FreeBSD systems
> > without modifying any rc.d scripts or configuration files.  Here is how
> > this is achieved:
> >
> >   1) rcd scans /etc/rc.d/ and /usr/local/etc/rc.d/ for existing
> >      rc.d scripts, parses their PROVIDE/REQUIRE/BEFORE/KEYWORD headers
> >      (same format as rcorder(8)), reads rc.conf(5) to determine the
> >      enabled state, and wraps each script as a virtual "legacy" unit
> >      in the dependency graph.
> >
> >   2) Legacy scripts are auto-classified during loading:
> >      - Scripts with pidfile= or command= -> "legacy-forking" units,
> >        tracked by rcd-exec(8) sub-reaper without pidfiles.
> >      - Scripts with only comments/blank lines -> barrier units.
> >      - Scripts without rcvar= are always enabled.
> >
> >   3) All rcctl(8) commands are passed through to the script directly:
> >          rcctl reload sshd -> /bin/sh /etc/rc.d/sshd reload
> >
> >   4) Template instances preserve the traditional calling convention:
> >          rcctl restart netif@em0 netif@em1
> >          -> /bin/sh /etc/rc.d/netif restart em0 em1
> >
> >   5) rcd reads /etc/defaults/rc.conf, /etc/rc.conf and
> >      /etc/rc.conf.local to determine legacy service enablement.
> >
> > The result: you can install rcd on a running system, reboot, and
> > everything works exactly as before -- except boot is faster because
> > services start in parallel where the dependency graph allows.
> >
> > Current status and what I need
> >
> > I would like to commit this to main soon so that people can start
> > testing it on their machines.  The initial commit will include:
> >
> >   - sbin/rcd/   -- the daemon, rcd-exec helper, unit tests
> >   - sbin/rcctl/ -- the control utility
> >   - Man pages: rcd.8, rcctl.8, rcd.conf.5, rcd.d.5, rcd-lua.3,
> >     rcd-exec.8
> >
> > It will NOT change init(8) or /etc/rc yet.  rcd will be built and
> > installed but not activated by default.  To test, you will be able to
> > run it manually or configure via kenv(8) to replace /etc/rc.
> >
> > Migration path
> >
> > The plan for a smooth transition:
> >
> > - 1 (now): Commit rcd(8) and rcctl(8) to the tree.  Build by
> >   default, but not activated.  Early adopters can test.
> >
> > - 2 (later): Commit init(8) changes to call rcd when present (falling
> >   back to /etc/rc otherwise).  Provide a /etc/rc wrapper that invokes
> >   rcd for systems that want to switch.
> >
> > - In the meantime: convert base system rc.d scripts to native
> >   UCL unit files adding the norcd keywords to the rc.d so that they
> >   are not read anymore by rcd(8).
> >
> > - Ultimately: rc.d -> unit files, at the pace each maintainer sees fit.
> >   There is no deadline; rc.d scripts are supported as long as needed.
> >
> > The code lives there:
> > https://reviews.freebsd.org/D56835
> >
> > Baptiste
> 
> This sounds turboawesome.  Parallel boot, pdfork, procctl, and UCL, all sound
> very exciting to me.  I can't wait to beta-test.  And
> backwards-compatibility, too, is super good.  IIRC, the lack of
> backwards-compatibility is why the NextBSD experiment failed.  Since
> you're the expert, could you tell us how else this rcd differs from
> NextBSD/launchd?

TL;DR rcd is less ambitious and fully design for FreeBSD (and probably FreeBSD
only).

launchd replaces cron(8) and inetd(8): it handles periodic jobs (StartInterval,
StartCalendarInterval) and socket-activated services (inetdCompatibility,
Sockets).  rcd focuses solely on service management and does not attempt to
replace cron or inetd. In the future, tighter integration with cron and inetd
(similar to Solaris SMF) could be considered, but this is not planned today.

launchd replaces init(8) entirely (PID 1).  rcd does not: init stays as PID 1,
rcd runs as a child like /etc/rc does today.

launchd uses XML plist files and Mach IPC (ported from macOS).  rcd uses UCL
files and a UNIX socket: no Mach dependency, no complex IPC layer to maintain.

launchd has no backward compatibility with rc.d scripts.  rcd is 100% backward
compatible: it scans /etc/rc.d/, parses rcorder(8) headers, reads rc.conf(5),
and wraps each script as a virtual unit.  All rcctl commands pass through to the
script directly.

launchd requires converting all services to plist format before it can be used.
rcd works with existing rc.d scripts out of the box: no migration needed to
start testing.

launchd uses fork/waitpid for process tracking.  rcd uses posix_spawn with
pdfork(2) process descriptors and procctl(2) subreaper: no PID file races, no
orphaned process escape.

rcd adds features not in launchd (or beyond what launchd does): native jail(2)
integration for service isolation (like recent addition to rcng), rctl(2)
resource control (like rcng), OOM protection via PROC_SPROTECT (like rcng),
embedded Lua for service hooks, template units for per-instance services
(dhclient@em0), and JSON Schema validation.

I am cooking an extention of rcd to allow sandboxing random applications a bit
like what systemd offers with seccomp, but this would be for later.