rust-systemd#
A pure Rust rewrite and drop-in replacement for systemd.
Overview#
rust-systemd is a fully compatible, binary-for-binary replacement for the entire systemd suite, written entirely in Rust.
The goal is to produce a set of binaries that can replace every systemd component on a Linux system
with zero configuration changes — existing unit files, configuration, and tooling should work unmodified.
The implementation is modeled after systemd (the C reference implementation maintained by Lennart Poettering et al.), following its architecture for the service manager, unit dependency graph, D-Bus APIs, journal binary log format, and all other public interfaces. This is a full drop-in replacement, not a reimagining — the same way rust-pkg-config replaces pkg-config.
Features#
- Full unit file parsing with all section types, specifier expansion, drop-in overrides, template instantiation, and
.d/directories - All unit types: service, socket, target, mount, automount, swap, timer, path, slice, scope, device
- Complete dependency graph with
Requires=,Wants=,After=,Before=,BindsTo=,PartOf=,Conflicts=,Requisite=, and all ordering/requirement semantics - All service types:
simple,exec,forking,oneshot,dbus,notify,notify-reload,idle - Full sd_notify protocol including
READY=1,RELOADING=1,STOPPING=1,STATUS=,ERRNO=,MAINPID=,WATCHDOG=,FDSTORE=,FDNAME=,BARRIER= - Socket activation with all socket types (stream, datagram, sequential-packet, FIFO, special, netlink, USB FFS) and inetd-style pass-through
- Journal logging with binary log format, structured fields, forward-secure sealing, field indexing, and full
journalctlquery language - D-Bus API compatibility implementing all systemd D-Bus interfaces (
org.freedesktop.systemd1,org.freedesktop.login1,org.freedesktop.hostname1, etc.) - cgroup v2 resource control with
MemoryMax=,CPUQuota=,IOWeight=,TasksMax=,Delegate=, and allsystemd.resource-controldirectives - Execution environment with all
systemd.execoptions: namespaces, capabilities, seccomp filters, credential passing,DynamicUser=,RootDirectory=,PrivateTmp=, etc. - Network configuration with
.network,.netdev, and.linkfiles, DHCP client/server, IPv6 RA, WireGuard, VLAN, bridge, bond, and all networkd features - DNS resolution with DNS-over-TLS, DNSSEC, mDNS, LLMNR, split DNS, per-link configuration
- Container management via
systemd-nspawnwith full OCI runtime compatibility - Boot performance analysis with
systemd-analyzeblame, critical-chain, plot, dot, security audit - Full
systemctlinterface with all subcommands, output modes, and--user/--systemscoping
Design Principles#
- Drop-in compatible — every binary, D-Bus interface, socket protocol, file format, and CLI flag must match systemd behavior exactly. Existing unit files, configuration, and tooling must work without modification.
- No C dependencies — pure Rust with direct syscall usage via
nix/rustixwhere needed. Nolibsystemd,libdbus,libudev, or other C library dependencies. - Same binary names — install as
systemd,systemctl,journalctl,systemd-journald, etc. with identical paths, so package managers and other software find them automatically. - Safe by default — leverage Rust's type system and ownership model to eliminate the classes of memory safety bugs that have historically affected systemd (CVE-2018-15688, CVE-2019-3842, CVE-2021-33910, etc.).
- Incremental adoption — individual components can be swapped in one at a time. Run the Rust
journaldwith the CsystemdPID 1, or vice versa.
Current Status#
🟢 NixOS boots successfully with rust-systemd as PID 1 — reaches multi-user.target with login prompt in ~8 seconds (cloud-hypervisor VM, full networking via networkd + resolved). 4,310 unit tests passing across 64 crates.
| Phase | Status | Highlights |
|---|---|---|
| Phase 0 — Foundation | ✅ Complete | Cargo workspace, libsystemd core library, unit parser, dependency graph |
| Phase 1 — Core System | ✅ Complete | PID 1, systemctl, journald, journalctl, shutdown, sleep, and 10 utility binaries |
| Phase 2 — Essential Services | 🔶 In progress | tmpfiles, sysusers, logind, user-sessions, random-seed, pstore, machine-id-setup, and more |
| Phase 3 — Network Stack | 🔶 Partial | networkd (DHCPv4), resolved (stub DNS), timesyncd, timedated, hostnamed, localed |
| Phase 4 — Extended Services | 🔶 Partial | machined, portabled, homed, oomd, coredump, sysext, dissect, firstboot, creds |
| Phase 5 — Utilities & Polish | 🔶 Partial | analyze, cgls, cgtop, mount, socket-activate, generator framework |
Unit File Directive Coverage#
189 of 425 upstream systemd directives supported (44%):
| Section | Supported | Total | Coverage |
|---|---|---|---|
| systemd.service | 25 | 34 | 74% |
| systemd.unit | 45 | 88 | 51% |
| systemd.exec | 74 | 147 | 50% |
| systemd.socket | 27 | 60 | 45% |
| systemd.kill | 3 | 7 | 43% |
| systemd.resource-control | 11 | 48 | 23% |
See PLAN.md for the full phased roadmap and per-component details. See CHANGELOG.md for recent changes.
Project Structure#
The project is organized as a Cargo workspace:
crates/
├── libsystemd/ # Core library: unit parsing, dependency graph, sd_notify,
│ # socket activation, platform abstractions, service lifecycle,
│ # unit name escaping/unescaping, configuration loading,
│ # journal entry model and on-disk storage engine
├── systemd/ # PID 1 service manager (init system)
├── systemctl/ # CLI control tool for the service manager
├── journald/ # Journal logging daemon (systemd-journald)
├── journalctl/ # Journal query tool
├── shutdown/ # System shutdown/reboot (systemd-shutdown)
├── sleep/ # Suspend/hibernate handler (systemd-sleep)
├── id128/ # 128-bit ID tool (systemd-id128)
├── escape/ # Unit name escaping tool (systemd-escape)
├── notify/ # Notification sender (systemd-notify)
├── path/ # Runtime path query tool (systemd-path)
├── cat/ # Journal cat tool (systemd-cat)
├── detect-virt/ # Virtualization detector (systemd-detect-virt)
├── delta/ # Unit file override viewer (systemd-delta)
├── run/ # Transient unit runner (systemd-run)
└── ac-power/ # AC power detection (systemd-ac-power)
See PLAN.md for the full 64-crate workspace layout including all Phase 2–5 components (udevd, logind, networkd, resolved, machined, portabled, homed, etc.).
Building#
# Build the entire workspace (all binaries)
cargo build --release
# Build just the service manager
cargo build --release -p systemd
# Build just the control tool
cargo build --release -p systemctl
# Build the journal daemon and query tool
cargo build --release -p systemd-journald
cargo build --release -p journalctl
# Build shutdown and sleep handlers
cargo build --release -p systemd-shutdown
cargo build --release -p systemd-sleep
# Build individual utilities
cargo build --release -p systemd-id128
cargo build --release -p systemd-escape
cargo build --release -p systemd-notify
cargo build --release -p systemd-path
cargo build --release -p systemd-cat
cargo build --release -p systemd-detect-virt
cargo build --release -p systemd-delta
cargo build --release -p systemd-run
cargo build --release -p systemd-ac-power
# Build with optional features
cargo build --release -p libsystemd --features dbus_support,cgroups,linux_eventfd
Testing#
# Run all tests
cargo test --workspace
# Run library tests only
cargo test -p libsystemd
# Run journal-related tests
cargo test -p libsystemd -- journal
cargo test -p systemd-journald
cargo test -p journalctl
# Run individual component tests
cargo test -p systemd-shutdown
cargo test -p systemd-sleep
Boot Testing with rust-nixos#
The rust-nixos project provides end-to-end boot testing by building a minimal NixOS image with rust-systemd as PID 1 and booting it in a cloud-hypervisor VM. Serial console output is captured so you can see every unit start, every log line, and any panics or failures during the boot process.
From the rust-nixos/ directory:
# Interactive boot (serial output on your terminal)
just run
# Automated boot test — streams output and reports pass/fail
just test
# Automated test with a custom timeout (seconds)
just test-timeout 180
# Save the full boot log to a file
just test-log /tmp/boot.log
# Quiet mode (no streaming, just exit code)
just test-quiet
# Boot test, then keep the VM running for interactive debugging
just test-keep
See PLAN.md — Integration Testing for details on what the boot test validates and the development workflow.
Debugging Early Boot Failures#
When a service crashes during early boot (e.g. SIGABRT before READY=1), stderr and journal are usually unavailable because the mount namespace has already hidden /dev/console and the journal socket. The exec helper uses a dedicated KmsgLogger (libsystemd::kmsg_log) that writes structured messages to /dev/kmsg (the kernel ring buffer), which survives mount namespace changes and is visible on the serial console via dmesg.
Enable tracing for a specific unit by adding SYSTEMD_LOG_LEVEL to its environment:
# In the unit's [Service] section (or via a drop-in override)
[Service]
Environment=SYSTEMD_LOG_LEVEL=trace
This produces detailed output at every exec-helper stage (mount namespace setup, privilege drop, credential loading, execv) on the serial console. You can also use debug, info, warn, or error.
When booting via rust-nixos, watch the serial output:
# From rust-nixos/
just run # interactive — scroll through serial output
just test-log /tmp/boot.log # save full boot log for post-mortem
Then grep the log for the failing unit:
grep 'rust-systemd\[systemd-timesyncd\]' /tmp/boot.log
How the log level flows (mirrors real systemd's --log-level to sd-executor):
service_manager (PID 1)
│
│ ExecHelperConfig { log_level: "info", ... } ← manager's own level
│ serialized as JSON over shmem fd
▼
exec_helper (forked child)
│
│ KmsgLogger::init(unit_name, manager_level)
│ 1. SYSTEMD_LOG_LEVEL env var (highest priority)
│ 2. log_level from config (from manager)
│ 3. built-in default: warn (lowest priority)
│
│ log::trace!("mount_ns: PrivateDevices=true...") → /dev/kmsg
│ log::trace!("mount_ns: ProtectKernelLogs=true...") → /dev/kmsg
│ ...
▼
execv(service_binary)
After ProtectKernelLogs= hides /dev/kmsg, the logger silently degrades — writes to kmsg fail and only stderr (warnings and above) remains. This is by design: the trace messages up to that point are the ones that matter for diagnosing sandbox setup crashes.
Quick reference:
| What you want | How |
|---|---|
| Trace a single unit | Environment=SYSTEMD_LOG_LEVEL=trace in the unit |
| Trace all units | Set SYSTEMD_LOG_LEVEL=trace in the manager's environment |
| See only warnings | Default behavior (no config needed) |
| Filter serial output | grep 'rust-systemd\[<unit>\]' <logfile> |
| Numeric syslog levels | 0–7 are accepted (7 = debug, 4 = warn) |
Contributing#
This is an ambitious project and contributions are very welcome. Good starting points:
- Phase 2 services — implement
udevd,tmpfiles,sysusers,logind, and other essential system services - Unit file parsing — add support for missing directives (see the directive coverage table above)
- New unit types — implement timer, mount, automount, swap, path, scope units
- systemctl — build out the full CLI with all systemctl subcommands
- Test coverage — port systemd's integration test suite
- Documentation — document behavior differences and compatibility notes
Coding Conventions#
D-Bus: use zbus only#
All D-Bus functionality must use the pure Rust zbus crate. Do not add dbus or dbus-crossroads as dependencies — the project migrated away from those C-based crates to eliminate libdbus initialization failures in sandboxed environments.
Key patterns:
- Define D-Bus interfaces with
#[zbus::interface]on impl blocks. - Use
zbus::blocking::Connectionfor synchronous connections. - Use
zbus::object_server::SignalEmitter(not the deprecatedSignalContext) for signal parameters in#[zbus(signal)]methods. - Use
zbus::blocking::fdo::DBusProxyfor standard D-Bus operations (name ownership, signal subscription).
License#
See LICENSE.