Blog 022: Process Management & Capabilities

M4 Phase 5 — prctl, capget/capset, UID/GID tracking, subreaper reparenting

systemd is a process manager. It needs to name its threads, mark itself as a subreaper, check capabilities, and track UIDs. Phase 5 adds all of this.

UID/GID Tracking

Previously every getuid/getgid returned hardcoded 0. Now the Process struct has real fields:

#![allow(unused)]
fn main() {
uid: AtomicU32,
euid: AtomicU32,
gid: AtomicU32,
egid: AtomicU32,
}

fork() copies parent values to child. setuid/setgid store the values. No permission checks yet — we're running everything as root — but the tracking is faithful enough for systemd's credential logic to work.

prctl(2)

systemd uses several prctl commands at startup:

CommandBehavior
PR_SET_NAMESet thread name (max 15 bytes), stored in comm field
PR_GET_NAMERead thread name, falls back to argv0
PR_SET_CHILD_SUBREAPERMark process as subreaper for orphan reparenting
PR_GET_CHILD_SUBREAPERQuery subreaper status
PR_SET_PDEATHSIGStub (accepted silently)
PR_GET_SECUREBITSReturns 0 (no secure bits)

The comm field is SpinLock<Option<Vec<u8>>>None means "use argv0", Some(bytes) is the explicitly set name. This shows up in /proc/[pid]/comm.

Subreaper Reparenting

The key architectural piece. When a process exits, its children become orphans. Linux normally reparents them to init (PID 1). With PR_SET_CHILD_SUBREAPER, systemd can intercept this — orphaned children of systemd's subtree get reparented to systemd instead.

#![allow(unused)]
fn main() {
fn find_subreaper_or_init(exiting: &Process) -> Arc<Process> {
    let mut ancestor = exiting.parent.upgrade();
    while let Some(p) = ancestor {
        if p.is_child_subreaper() {
            return p;
        }
        ancestor = p.parent.upgrade();
    }
    // Fall back to init (PID 1)
    PROCESSES.lock().get(&PId::new(1)).unwrap().clone()
}
}

This walks up the parent chain looking for the nearest subreaper. The reparented children are moved to the new parent's children list, and JOIN_WAIT_QUEUE is woken so wait() can see them.

Linux Capabilities (Stub)

systemd checks capabilities with capget() to decide what it's allowed to do. Our stub returns all capabilities granted:

  • Version 3 protocol (0x20080522)
  • Two 32-bit sets, both effective = 0xFFFFFFFF, permitted = 0xFFFFFFFF
  • capset() accepts silently

Real capability enforcement comes later with multi-user support.

Syscall Summary

Syscallx86_64ARM64
prctl157167
capget12590
capset12691

~270 lines across 5 files. The subreaper logic is the most architecturally important addition — it's how systemd maintains its process hierarchy even when intermediate launcher processes exit.