listen-fds 0.1.0

A Rust library for handling systemd socket activation
Documentation

listen-fds

A Rust library for handling systemd socket activation.

What is Socket Activation?

Socket activation is a systemd feature that allows services to:

  • Start on-demand when connections arrive
  • Perform zero-downtime restarts (systemd holds the socket during restart)
  • Improve resource utilization (services don't run until needed)

When systemd socket-activates your service, it passes listening sockets (or other file descriptors) via environment variables. This library provides a safe, idiomatic Rust interface for receiving those file descriptors.

Features

  • Safe ownership of socket-activated file descriptors
  • Automatic PID validation (supports both LISTEN_PID and the newer LISTEN_PIDFDID)
  • Named file descriptor lookup via LISTEN_FDNAMES
  • Automatic FD_CLOEXEC setting for security
  • Zero-copy borrowing or ownership transfer of FDs
  • FD retrieval methods return both the file descriptor and its name (or "unknown" if unnamed)

Installation

Add to your Cargo.toml:

[dependencies]
listen-fds = { git = "https://github.com/swick/listen-fds-rs.git" }

Usage

Basic Example

use listen_fds::ListenFds;
use std::os::unix::net::UnixListener;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // SAFETY: Must be called early in main(), before spawning threads
    let mut fds = unsafe { ListenFds::new()? };

    println!("Received {} file descriptors", fds.len());

    // Take ownership of the first FD
    if let Some((fd, name)) = fds.take_fd(0) {
        println!("Taking FD named: {}", name);
        let listener = UnixListener::from(fd);
        // Use the listener...
    }

    Ok(())
}

Using Named File Descriptors

use listen_fds::ListenFds;
use std::os::unix::net::TcpListener;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // SAFETY: Must be called early in main(), before spawning threads
    let mut fds = unsafe { ListenFds::new()? };

    // Get all FDs named "http"
    for fd in fds.take("http") {
        let listener = TcpListener::from(fd);
        // Handle HTTP connections...
    }

    Ok(())
}

Borrowing File Descriptors

use listen_fds::ListenFds;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // SAFETY: Must be called early in main(), before spawning threads
    let fds = unsafe { ListenFds::new()? };

    // Borrow an FD without taking ownership
    if let Some((fd, name)) = fds.get_fd(0) {
        println!("FD 0 name: {}", name);
        // Use borrowed fd...
    }

    Ok(())
}

Systemd Configuration Example

Socket Unit (myservice.socket)

[Unit]
Description=My Service Socket

[Socket]
ListenStream=/run/myservice.sock
FileDescriptorName=main

[Install]
WantedBy=sockets.target

Service Unit (myservice.service)

[Unit]
Description=My Service
Requires=myservice.socket

[Service]
Type=simple
ExecStart=/usr/bin/myservice

Enable socket activation:

systemctl enable --now myservice.socket

Safety

The ListenFds::new() function is marked unsafe because:

  1. It modifies the process environment by removing LISTEN_* variables
  2. It must be called before spawning any threads (to avoid race conditions with environment access)
  3. It must be called at most once per process

Call it early in your main() function, before any thread spawning occurs.

PID Validation

The library validates that file descriptors are intended for your process using:

  • LISTEN_PID: Traditional PID-based validation
  • LISTEN_PIDFDID: Modern pidfd-based validation (more secure, resistant to PID reuse attacks)

If either validation fails, ListenFds::new() returns an error.

License

Licensed under either of:

at your option.

References