rcd(8) - new service manager daemon

Baptiste Daroussin <[email protected]> Sun, 14 Jun 2026 21:26:29 +0200
Newsgroups gmane.os.freebsd.devel.hackers
Message-ID <[email protected]>
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