patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: Demi Marie Obenour <demiobenour@gmail.com>
To: Spectrum OS Development <devel@spectrum-os.org>
Cc: Alyssa Ross <hi@alyssa.is>, Demi Marie Obenour <demiobenour@gmail.com>
Subject: [PATCH v2] Set up control groups for most services
Date: Sat, 20 Jun 2026 13:27:32 -0400	[thread overview]
Message-ID: <20260620-cgroups-v2-1-ccae224b6c85@gmail.com> (raw)
In-Reply-To: <20260620-cgroups-v1-1-0e5abf35101b@gmail.com>

The cgroups are handled by a Rust tool.  The name of the cgroup is
autogenerated from the service name.

Using cgroups for process control is not implemented yet.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
Changes in v2:
- Omit resource control support.  It was completely broken, and the way
  Spectrum run scripts work means that it is better to setup resource
  controls just before the execve() into the final service process.
  This will be done by a separate tool.
- Link to v1: https://spectrum-os.org/lists/archives/spectrum-devel/20260620-cgroups-v1-1-0e5abf35101b@gmail.com
---
 .codespellrc                                       |   2 +-
 host/rootfs/default.nix                            |   6 +-
 host/rootfs/file-list.mk                           |  12 ++
 host/rootfs/image/etc/fstab                        |   1 +
 .../s6-linux-init/run-image/service/getty-tty2/run |   1 +
 .../s6-linux-init/run-image/service/getty-tty3/run |   1 +
 .../s6-linux-init/run-image/service/getty-tty4/run |   1 +
 .../run-image/service/root-terminal/run            |   1 +
 .../run-image/service/s6-linux-init-shutdownd/run  |   1 +
 .../run-image/service/s6-svscan-log/run            |   1 +
 .../service/serial-getty-generator/finish          |   5 +
 .../run-image/service/serial-getty-generator/run   |   3 +-
 .../run-image/service/serial-getty/finish          |   5 +
 .../run-image/service/serial-getty/run             |   3 +-
 .../run-image/service/serial-getty/template/run    |   1 +
 .../run-image/service/vm-services/finish           |   5 +
 .../run-image/service/vm-services/run              |   3 +-
 .../vm-services/template/data/service/dbus/finish  |   5 +
 .../vm-services/template/data/service/dbus/run     |   3 +-
 .../template/data/service/spectrum-router/finish   |   5 +
 .../template/data/service/spectrum-router/run      |   3 +-
 .../template/data/service/vhost-user-fs/finish     |   5 +
 .../template/data/service/vhost-user-fs/run        |   1 +
 .../template/data/service/vhost-user-gpu/finish    |   5 +
 .../template/data/service/vhost-user-gpu/run       |   4 +-
 .../xdg-desktop-portal-spectrum-host/finish        |   5 +
 .../service/xdg-desktop-portal-spectrum-host/run   |   2 +
 .../run-image/service/vm-services/template/finish  |   5 +
 .../run-image/service/vm-services/template/run     |   1 +
 .../etc/s6-linux-init/run-image/service/vmm/finish |   5 +
 .../etc/s6-linux-init/run-image/service/vmm/run    |   3 +-
 host/rootfs/image/etc/s6-rc/systemd-udevd/finish   |   5 +
 host/rootfs/image/etc/s6-rc/systemd-udevd/run      |   5 +-
 host/rootfs/image/etc/s6-rc/weston/finish          |   5 +
 host/rootfs/image/etc/s6-rc/weston/run             |   3 +-
 pkgs/default.nix                                   |   1 +
 tools/cgroup-setup/Cargo.lock                      |  67 +++++++
 tools/cgroup-setup/Cargo.lock.license              |   2 +
 tools/cgroup-setup/Cargo.toml                      |  17 ++
 tools/cgroup-setup/default.nix                     |  18 ++
 tools/cgroup-setup/src/cgroup.rs                   | 181 +++++++++++++++++++
 tools/cgroup-setup/src/main.rs                     | 196 +++++++++++++++++++++
 42 files changed, 590 insertions(+), 14 deletions(-)

diff --git a/.codespellrc b/.codespellrc
index d8023afc64ec44e98a88c5e397c8d5c681dde063..ae20da8309530759ac729689825a4afc683afa09 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -2,4 +2,4 @@
 # SPDX-License-Identifier: CC0-1.0
 
 [codespell]
-ignore-words-list = crate,passt,rouge,ser
+ignore-words-list = crate,passt,rouge,ser,WRONLY
diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix
index 6bfeefbe0a5f76c1538ccb40e5eb8f291f5d3592..ccf626e2ec0f4bf96573dc5edf058c9375bb65f6 100644
--- a/host/rootfs/default.nix
+++ b/host/rootfs/default.nix
@@ -8,7 +8,7 @@ import ../../lib/call-package.nix (
 }:
 pkgsMusl.callPackage (
 
-{ spectrum-host-tools, spectrum-router
+{ spectrum-host-tools, spectrum-router, spectrum-cgroup-setup
 , lib, stdenvNoCC, nixos, runCommand, writeClosure, erofs-utils, s6-rc
 , btrfs-progs, bubblewrap, busybox, cloud-hypervisor, cosmic-files
 , crosvm, cryptsetup, dejavu_fonts, dbus, execline, foot, fuse3
@@ -27,8 +27,8 @@ let
   packages = [
     btrfs-progs bubblewrap cloud-hypervisor cosmic-files crosvm cryptsetup dbus
     execline fuse3 inotify-tools iproute2 jq kmod mdevd mount-flatpak s6
-    s6-linux-init s6-rc shadow socat spectrum-host-tools spectrum-router
-    virtiofsd xdg-desktop-portal-spectrum-host
+    s6-linux-init s6-rc shadow socat spectrum-cgroup-setup spectrum-host-tools
+    spectrum-router virtiofsd xdg-desktop-portal-spectrum-host
 
     (foot.override { allowPgo = false; })
 
diff --git a/host/rootfs/file-list.mk b/host/rootfs/file-list.mk
index 3899d620717fc97f42e669e5313c4100dcf5b1cd..acf5534e7b966811c545daf2d3aa4f26ca59e15e 100644
--- a/host/rootfs/file-list.mk
+++ b/host/rootfs/file-list.mk
@@ -21,25 +21,35 @@ FILES = \
 	image/etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run \
 	image/etc/s6-linux-init/run-image/service/s6-svscan-log/notification-fd \
 	image/etc/s6-linux-init/run-image/service/s6-svscan-log/run \
+	image/etc/s6-linux-init/run-image/service/serial-getty-generator/finish \
 	image/etc/s6-linux-init/run-image/service/serial-getty-generator/run \
+	image/etc/s6-linux-init/run-image/service/serial-getty/finish \
 	image/etc/s6-linux-init/run-image/service/serial-getty/notification-fd \
 	image/etc/s6-linux-init/run-image/service/serial-getty/run \
 	image/etc/s6-linux-init/run-image/service/serial-getty/template/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/run \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/down \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/run \
+	image/etc/s6-linux-init/run-image/service/vm-services/template/finish \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vm-services/template/run \
+	image/etc/s6-linux-init/run-image/service/vmm/finish \
 	image/etc/s6-linux-init/run-image/service/vmm/notification-fd \
 	image/etc/s6-linux-init/run-image/service/vmm/run \
 	image/etc/s6-linux-init/run-image/service/vmm/template/notification-fd \
@@ -96,6 +106,7 @@ S6_RC_FILES = \
 	image/etc/s6-rc/systemd-udevd-coldplug/dependencies.d/systemd-udevd \
 	image/etc/s6-rc/systemd-udevd-coldplug/type \
 	image/etc/s6-rc/systemd-udevd-coldplug/up \
+	image/etc/s6-rc/systemd-udevd/finish \
 	image/etc/s6-rc/systemd-udevd/notification-fd \
 	image/etc/s6-rc/systemd-udevd/run \
 	image/etc/s6-rc/systemd-udevd/type \
@@ -108,6 +119,7 @@ S6_RC_FILES = \
 	image/etc/s6-rc/vmm-env/contents.d/systemd-udevd-coldplug \
 	image/etc/s6-rc/vmm-env/type \
 	image/etc/s6-rc/weston/dependencies.d/systemd-udevd-coldplug \
+	image/etc/s6-rc/weston/finish \
 	image/etc/s6-rc/weston/notification-fd \
 	image/etc/s6-rc/weston/run \
 	image/etc/s6-rc/weston/type
diff --git a/host/rootfs/image/etc/fstab b/host/rootfs/image/etc/fstab
index 18bb5e45abafeaf43871306ce8233239e5c07f76..d92364d83f4d26f766fcd5b494614c06a630d2fe 100644
--- a/host/rootfs/image/etc/fstab
+++ b/host/rootfs/image/etc/fstab
@@ -6,3 +6,4 @@ tmpfs	/dev/shm	tmpfs	nosuid,nodev					0	0
 tmpfs	/media		tmpfs	nosuid,nodev,noexec,nosymfollow,mode=755	0	0
 sysfs	/sys		sysfs	nosuid,nodev,noexec				0	0
 tmpfs	/tmp		tmpfs	nosuid,nodev					0	0
+cgroup2 /sys/fs/cgroup  cgroup2 nosuid,nodev,noexec,nosymfollow			0	0
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty2/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty2/run
index 4d2cd7172e887e90d9ce87489cdacf41e51f77e8..5b2421a3dedf7c282ea818f4064535d0fc29d511 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty2/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty2/run
@@ -2,4 +2,5 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
 
+# no cgroups so that nohup works
 getty -i -n -l /etc/login 0 tty2 linux
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty3/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty3/run
index 06abf5422365ff4edc9379e5cbcab716e6830399..3ca382f76368174ee1f99ea880827958c57b602c 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty3/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty3/run
@@ -2,4 +2,5 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
 
+# no cgroups so that nohup works
 getty -i -n -l /etc/login 0 tty3 linux
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty4/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty4/run
index 3dd235fc93d403fc8971e923ec62cb025684b67a..983bd82927e468bab6ae20f61411680d173e4924 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty4/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/getty-tty4/run
@@ -2,4 +2,5 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
 
+# no cgroups so that nohup works
 getty -i -n -l /etc/login 0 tty4 linux
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/root-terminal/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/root-terminal/run
index 86b9a1ef70300de52f33a84cbfac22a77e00df36..fb55327dff5f189f543fb3629702caad9ff247e2 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/root-terminal/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/root-terminal/run
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
 
+# cgroups make no sense here
 s6-ipcserver-socketbinder -a 0700 /run/root-terminal
 
 if { chown wayland /run/root-terminal }
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run
index 327f4ccc5056e7ef87c089e666090307845cae3a..a6733604e91692f8440fd68efeb1a6b7378be167 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-linux-init-shutdownd/run
@@ -2,4 +2,5 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
 
+# cgroups make no sense for shutdownd
 s6-linux-init-shutdownd -Bc /etc/s6-linux-init
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-svscan-log/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-svscan-log/run
index 8f7bd889e50d76a49b98d8ba9e2c6172f287277c..cf3f5c7b499d997fd426062651eca64b3accc146 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-svscan-log/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/s6-svscan-log/run
@@ -2,5 +2,6 @@
 # SPDX-License-Identifier: ISC
 # SPDX-FileCopyrightText: Copyright (c) 2015-2024 Laurent Bercot <ska-skaware@skarnet.org>
 
+# cgroups make no sense for a logger
 redirfd -rnb 0 fifo
 s6-log -bpd3 -- T /run/log
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/run
index bf66ab0878fc6d8e77ca71a4a89e50c139a6bb11..5c9988c994ed9b942329055673b332d0aa918957 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/run
@@ -1,7 +1,8 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2024-2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 piperw 3 4
 background {
   fdclose 3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/run
index 78f794202bf174f3c036f3e20755ac087a988277..ed0808b9e45b077023f237a0f9c0e5c830015ac2 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/run
@@ -1,5 +1,6 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 s6-svscan -d3 instance
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/template/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/template/run
index f3ed9a7f08e60206afd222f4fc02c5fb19527561..775b828c72b9d73bef8a3355fb3ae0536975f474 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/template/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty/template/run
@@ -2,4 +2,5 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2020-2021, 2024 Alyssa Ross <hi@alyssa.is>
 
+# Don't put this in a cgroup.  We want nohup to work.
 getty -i -n -l /etc/login 0,115200,57600,38400,9600 $1 dumb
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/run
index 78f794202bf174f3c036f3e20755ac087a988277..ed0808b9e45b077023f237a0f9c0e5c830015ac2 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/run
@@ -1,5 +1,6 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 s6-svscan -d3 instance
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/run
index f0507dc92aadf25c092f0719eb767542ac0c4451..89676ac91d3e22db7726fc869a065137b031d0be 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/dbus/run
@@ -1,7 +1,8 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2024-2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 importas -i VM VM
 
 if {
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/run
index 3de58c27facc78c48176d8f9a6dd1827ac926f1a..21e37ab8a3dad1ab701a0989fb408fed100657cf 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/spectrum-router/run
@@ -1,8 +1,9 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev>
 # SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 importas -i VM VM
 
 s6-ipcserver-socketbinder -a 0770 /run/vm/by-id/${VM}/router-driver.sock
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/run
index cb059aebc8dfe2c8a5b059d5bfea884de53bf922..d4321263269ae46dd6a1b72e39c57c1c07cb06ba 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-fs/run
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2021-2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 s6-ipcserver-socketbinder -a 0700 -B env/virtiofsd.sock
 
 importas -i VM VM
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/run
index c3bfafe02ec6d2ee418fe56b033bbc4c7a73b186..ad705da8460a86667b47d40e61fee167d240a9c6 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/vhost-user-gpu/run
@@ -1,8 +1,10 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
 # SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
 
+cgroup-setup -- $1
+
 s6-ipcserver-socketbinder -a 0700 -b 1 env/crosvm.sock
 
 multisubstitute {
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/run
index 909957b817136149dd01c3b98847238ecda8032d..54860d67f284e7cabbfc955f44ef819ff3f593bc 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/data/service/xdg-desktop-portal-spectrum-host/run
@@ -2,6 +2,8 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2024-2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
+
 importas -i VM VM
 
 export DBUS_SESSION_BUS_ADDRESS unix:path=/run/portal-bus/${VM}
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/run
index bf90e99eef4106a9ad17dfba310b6edb3eaa64a3..13f2cec03a087be4602c14ed78749cce1f6a26a1 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/template/run
@@ -2,6 +2,7 @@
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 export VM $1
 
 s6-svscan -d3 data/service
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/finish b/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/run b/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/run
index 78f794202bf174f3c036f3e20755ac087a988277..ed0808b9e45b077023f237a0f9c0e5c830015ac2 100755
--- a/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/run
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/run
@@ -1,5 +1,6 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 s6-svscan -d3 instance
diff --git a/host/rootfs/image/etc/s6-rc/systemd-udevd/finish b/host/rootfs/image/etc/s6-rc/systemd-udevd/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-rc/systemd-udevd/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-rc/systemd-udevd/run b/host/rootfs/image/etc/s6-rc/systemd-udevd/run
old mode 100644
new mode 100755
index aec6444e951503eae988e666b77fda8f2ae33d72..c37909af23fa611a75442108100b4fd9db1e22f8
--- a/host/rootfs/image/etc/s6-rc/systemd-udevd/run
+++ b/host/rootfs/image/etc/s6-rc/systemd-udevd/run
@@ -1,7 +1,8 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
-s6-setlock /run/sd-notify-wrapper/systemd-udevd.lock
+
+cgroup-setup -- $1
 s6-ipcserver-socketbinder -b0 -m -a 0600 /run/sd-notify-wrapper/systemd-udevd.sock
 background -d {
    fdmove 1 3
diff --git a/host/rootfs/image/etc/s6-rc/weston/finish b/host/rootfs/image/etc/s6-rc/weston/finish
new file mode 100755
index 0000000000000000000000000000000000000000..0c16cc8060762eac3a91a49b54048407dfbe25c5
--- /dev/null
+++ b/host/rootfs/image/etc/s6-rc/weston/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WS3
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- $3
diff --git a/host/rootfs/image/etc/s6-rc/weston/run b/host/rootfs/image/etc/s6-rc/weston/run
old mode 100644
new mode 100755
index fd59586c719391deb546c29578341e16a61ed4ce..6794a3ed729c9b99336cb64df95bbb3eba644582
--- a/host/rootfs/image/etc/s6-rc/weston/run
+++ b/host/rootfs/image/etc/s6-rc/weston/run
@@ -1,7 +1,8 @@
-#!/bin/execlineb -WP
+#!/bin/execlineb -WS1
 # SPDX-License-Identifier: EUPL-1.2+
 # SPDX-FileCopyrightText: 2021, 2025 Alyssa Ross <hi@alyssa.is>
 
+cgroup-setup -- $1
 importas -Siu WAYLAND_DISPLAY
 
 piperw 4 3
diff --git a/pkgs/default.nix b/pkgs/default.nix
index 4dbdfee8ba330f5ba5c9fedee2d1bb8a44af5722..22c0d782bf4b5467fe77d5a2d71fb8df3af3e756 100644
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -51,6 +51,7 @@ let
       driverSupport = true;
     };
     spectrum-router = self.callSpectrumPackage ../tools/router {};
+    spectrum-cgroup-setup = self.callSpectrumPackage ../tools/cgroup-setup {};
     xdg-desktop-portal-spectrum-host =
       self.callSpectrumPackage ../tools/xdg-desktop-portal-spectrum-host {};
 
diff --git a/tools/cgroup-setup/Cargo.lock b/tools/cgroup-setup/Cargo.lock
new file mode 100644
index 0000000000000000000000000000000000000000..fe967b3aa02c296c87b6b36ac59253dbe0a32de9
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.lock
@@ -0,0 +1,67 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "cgroup-setup"
+version = "0.0.0"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/tools/cgroup-setup/Cargo.lock.license b/tools/cgroup-setup/Cargo.lock.license
new file mode 100644
index 0000000000000000000000000000000000000000..aa108acd23886d8302eaf7babff90d1b08ae19fb
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.lock.license
@@ -0,0 +1,2 @@
+SPDX-License-Identifier: EUPL-1.2+
+SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
diff --git a/tools/cgroup-setup/Cargo.toml b/tools/cgroup-setup/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..9cce0be706151dfd5264a81afb3ee9a6c851367c
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.toml
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+[package]
+name = "cgroup-setup"
+edition = "2024"
+
+[dependencies]
+libc = "0.2.177"
+rustix = { version = "1.1.2", features = ["fs"] }
+
+[profile.release]
+split-debuginfo = "symbols"
+strip = "symbols"
+lto = true
+panic = "abort"
diff --git a/tools/cgroup-setup/default.nix b/tools/cgroup-setup/default.nix
new file mode 100644
index 0000000000000000000000000000000000000000..9e716b8f270828b733f39961e5ca37a290a872b4
--- /dev/null
+++ b/tools/cgroup-setup/default.nix
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: 2024 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2025 Yureka Lilian <yureka@cyberchaos.dev>
+# SPDX-License-Identifier: MIT
+
+import ../../lib/call-package.nix (
+{ src, lib, rustPlatform }:
+
+rustPlatform.buildRustPackage {
+  name = "spectrum-cgroup-setup";
+
+  src = lib.fileset.toSource {
+    root = ../..;
+    fileset = lib.fileset.intersection src ./.;
+  };
+  sourceRoot = "source/tools/cgroup-setup";
+
+  cargoLock.lockFile = ./Cargo.lock;
+}) (_: {})
diff --git a/tools/cgroup-setup/src/cgroup.rs b/tools/cgroup-setup/src/cgroup.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cd77becc0b40bfdcd983e53b63b7c21e4308d5fc
--- /dev/null
+++ b/tools/cgroup-setup/src/cgroup.rs
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: EUPL-1.2+
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+use std::fs::File;
+use std::io::{Read, Seek};
+use std::os::fd::{AsFd, OwnedFd};
+use std::os::fd::{AsRawFd, BorrowedFd};
+
+use std::path::{Path, PathBuf};
+
+use rustix::fs::AtFlags;
+use rustix::path::Arg;
+use rustix::{
+    fs::{Mode, OFlags, ResolveFlags},
+    io::Errno,
+};
+
+pub(crate) struct LeafCgroup {
+    path: PathBuf,
+    fd: OwnedFd,
+}
+
+enum Access {
+    Read,
+    Write,
+}
+
+impl LeafCgroup {
+    fn open_subtree(&self, path: &std::path::Path, access: Access) -> Result<OwnedFd, Errno> {
+        rustix::fs::openat2(
+            self.fd.as_fd(),
+            path,
+            OFlags::NOATIME
+                | OFlags::CLOEXEC
+                | OFlags::NOFOLLOW
+                | match access {
+                    Access::Write => OFlags::WRONLY,
+                    Access::Read => OFlags::RDONLY,
+                },
+            Mode::empty(),
+            ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+        )
+    }
+
+    pub fn kill_processes(&self) -> std::io::Result<()> {
+        let kill_fd = self.open_subtree(std::path::Path::new("cgroup.kill"), Access::Write)?;
+        let wait_file = self.open_subtree(std::path::Path::new("cgroup.events"), Access::Read)?;
+        let poll_fd = wait_file.as_raw_fd();
+        let mut wait_fd = File::from(wait_file);
+        assert_eq!(rustix::io::write(kill_fd.as_fd(), b"1")?, 1, "kernel bug");
+        let mut fds = libc::pollfd {
+            fd: poll_fd,
+            events: libc::POLLIN | libc::POLLPRI | libc::POLLRDHUP,
+            revents: 0,
+        };
+        // SAFETY: FFI call, valid arguments, fds contains 1 element
+        let mut v = vec![];
+        'a: loop {
+            v.clear();
+            if unsafe { libc::poll(&raw mut fds, 1, -1) } != 1 {
+                panic!("poll failed");
+            }
+            wait_fd
+                .seek(std::io::SeekFrom::Start(0))
+                .expect("Seek on control group file should succeed");
+            wait_fd
+                .read_to_end(&mut v)
+                .expect("reading from control group should work");
+            for substr in v.split(|&c| c == b'\n') {
+                if substr == b"populated 0" {
+                    break 'a;
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl AsFd for LeafCgroup {
+    fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
+        self.fd.as_fd()
+    }
+}
+
+impl LeafCgroup {
+    pub(crate) fn open_cgroup_at(&self, p: &Path) -> Result<Self, Errno> {
+        let fd = rustix::fs::openat2(
+            self.fd.as_fd(),
+            p,
+            OFlags::NOATIME
+                | OFlags::CLOEXEC
+                | OFlags::NOFOLLOW
+                | OFlags::RDONLY
+                | OFlags::DIRECTORY,
+            Mode::empty(),
+            ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+        )?;
+        Ok(Self {
+            fd,
+            path: self.path.join(p),
+        })
+    }
+    pub(crate) fn open_cgroup(fd: BorrowedFd, p: &Path) -> Result<Self, Errno> {
+        let fd = rustix::fs::openat2(
+            fd,
+            p,
+            OFlags::NOATIME
+                | OFlags::CLOEXEC
+                | OFlags::NOFOLLOW
+                | OFlags::RDONLY
+                | OFlags::DIRECTORY,
+            Mode::empty(),
+            //ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+            ResolveFlags::NO_SYMLINKS,
+        )?;
+        Ok(Self {
+            fd,
+            path: p.to_owned(),
+        })
+    }
+    pub(crate) fn make_child(&self, p: &Path) -> Result<(), Errno> {
+        rustix::fs::mkdirat(
+            self.fd.as_fd(),
+            p,
+            Mode::RUSR
+                | Mode::WUSR
+                | Mode::XUSR
+                | Mode::RGRP
+                | Mode::XGRP
+                | Mode::ROTH
+                | Mode::XOTH,
+        )
+    }
+
+    pub(crate) fn delete_child(&self, path: &Path) -> Result<(), Errno> {
+        remove_all(100, self.as_fd(), &path.as_cow_c_str().unwrap())
+    }
+}
+
+fn remove_recursively(fd: OwnedFd, remaining_depth: usize) -> Result<(), Errno> {
+    if remaining_depth < 1 {
+        panic!("control groups too deeply nested");
+    }
+    let mut d = rustix::fs::Dir::new(fd).expect("cannot start iterating");
+    while let Some(element) = d.next() {
+        let element = element.expect("Iterating through a cgroup directory failed?");
+        if element.file_type() != rustix::fs::FileType::Directory {
+            continue;
+        }
+
+        let remaining_depth = remaining_depth - 1;
+        let d: &rustix::fs::Dir = &d;
+        let dirfd = d.fd().unwrap();
+        let path = element.file_name();
+        remove_all(remaining_depth, dirfd, path)?;
+    }
+    Ok(())
+}
+
+fn remove_all(
+    remaining_depth: usize,
+    dirfd: BorrowedFd<'_>,
+    path: &std::ffi::CStr,
+) -> Result<(), Errno> {
+    if path == c"." || path == c".." {
+        return Ok(());
+    }
+    if rustix::fs::unlinkat(dirfd, path, AtFlags::REMOVEDIR).is_ok() {
+        return Ok(());
+    }
+    let fd = rustix::fs::openat2(
+        dirfd,
+        path,
+        OFlags::NOATIME | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::RDONLY | OFlags::DIRECTORY,
+        Mode::empty(),
+        ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+    )?;
+    remove_recursively(fd, remaining_depth)?;
+    rustix::fs::unlinkat(dirfd, path, AtFlags::REMOVEDIR)?;
+    Ok(())
+}
diff --git a/tools/cgroup-setup/src/main.rs b/tools/cgroup-setup/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..358703a73fcbdc38794312fe3987e733f37c2df0
--- /dev/null
+++ b/tools/cgroup-setup/src/main.rs
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: EUPL-1.2+
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+use std::{
+    ffi::{OsStr, OsString},
+    fs::File,
+    io::Write as _,
+    os::unix::prelude::*,
+    path::{Component, Path, PathBuf},
+};
+
+use rustix::{
+    fs::{FlockOperation, Mode, OFlags, ResolveFlags},
+    io::Errno,
+};
+
+mod cgroup;
+
+macro_rules! fail {
+    ($($arg:tt)*) => {{
+        eprintln!($($arg)*);
+        std::process::exit(1)}
+    };
+}
+
+fn main() {
+    let mut args = std::env::args_os();
+    let Some(prog_name) = args.next() else {
+        fail!("No command line arguments (argv[0] is NULL)");
+    };
+    let mut cgroup_relative_path = args.next();
+    if cgroup_relative_path.as_deref() == Some(OsStr::from_bytes(b"--")) {
+        cgroup_relative_path = args.next();
+    }
+
+    let Some(cgroup_relative_path) = cgroup_relative_path else {
+        fail!("{prog_name:?}: Have no positional arguments, expect at least 1")
+    };
+    let mut cgroup_relative_path = cgroup_relative_path.into_vec();
+
+    // slow but we do not care
+    if cgroup_relative_path.len() > 247 {
+        fail!("{prog_name:?}: Cgroup name {cgroup_relative_path:?} too long");
+    }
+    if cgroup_relative_path.is_empty() {
+        fail!("{prog_name:?}: Cgroup name {cgroup_relative_path:?} empty");
+    }
+    if cgroup_relative_path[0] == b'-' {
+        fail!("{prog_name:?}: Cgroup name {cgroup_relative_path:?} starts with '-'");
+    }
+    for &i in &cgroup_relative_path {
+        match i {
+            b'/' => fail!(
+                "{prog_name:?}: Cgroup name {cgroup_relative_path:?} \
+contains /"
+            ),
+            b'$' => fail!(
+                "{prog_name:?}: Cgroup name {cgroup_relative_path:?} \
+contains $: did you forget an execline substitution?"
+            ),
+            b'!'..=b'~' => {}
+            _ => fail!(
+                "{prog_name:?}: Cgroup name {cgroup_relative_path:?} \
+contains space, non-ASCII character, or control character (byte {i})"
+            ),
+        }
+    }
+    cgroup_relative_path.extend_from_slice(b".service");
+    let cgroup_relative_path = Path::new(OsStr::from_bytes(&cgroup_relative_path));
+
+    let local_cgroup = std::fs::read("/proc/thread-self/cgroup")
+        .unwrap_or_else(|e| fail!("{prog_name:?}: cannot read /proc/thread-self/cgroup: {e}"));
+
+    if !local_cgroup.starts_with(b"0::/")
+        || !local_cgroup.ends_with(b"\n")
+        || local_cgroup.contains(&b'\0')
+    {
+        fail!("{prog_name:?}: Kernel bug: Bad contents of /proc/thread-self/cgroup");
+    }
+
+    let mut local_cgroup = PathBuf::from(<OsString as OsStringExt>::from_vec(
+        local_cgroup[4..local_cgroup.len() - 1].to_owned(),
+    ));
+
+    match local_cgroup.components().next_back() {
+        Some(Component::Prefix(_)) => unreachable!("does not occur on Linux"),
+        Some(Component::RootDir) | None => {}
+        Some(Component::CurDir) => unreachable!("not produced by kernel"),
+        Some(Component::ParentDir) => unreachable!("not produced by kernel"),
+        Some(Component::Normal(os_str)) => {
+            if os_str.as_bytes() == b"@inner.service" {
+                let _ = local_cgroup.pop();
+            }
+        }
+    }
+
+    if local_cgroup.as_os_str().is_empty() {
+        local_cgroup = PathBuf::from(".");
+    }
+
+    let cgroup_root = rustix::fs::openat2(
+        rustix::fs::CWD,
+        Path::new("/sys/fs/cgroup"),
+        OFlags::DIRECTORY | OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
+        Mode::empty(),
+        ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
+    )
+    .unwrap_or_else(|e| fail!("{prog_name:?}: cannot open /sys/fs/cgroup: {e}"));
+
+    let cgroup = match cgroup::LeafCgroup::open_cgroup(cgroup_root.as_fd(), &local_cgroup) {
+        Ok(e) => e,
+        Err(e) => fail!(
+            "{prog_name:?}: Failed to open {}: {e:?}",
+            local_cgroup.display()
+        ),
+    };
+
+    match rustix::fs::flock(cgroup.as_fd(), FlockOperation::LockExclusive) {
+        Ok(()) => {}
+        Err(e) => fail!("{prog_name:?}: cannot lock cgroup: {e}"),
+    }
+    purge_cgroup(&prog_name, &cgroup, cgroup_relative_path);
+    let Some(program_name) = args.next() else {
+        return;
+    };
+    if let Err(e) = cgroup.make_child(cgroup_relative_path) {
+        fail!("{prog_name:?}: cannot create child cgroup: {e}")
+    }
+    let child = cgroup
+        .open_cgroup_at(cgroup_relative_path)
+        .unwrap_or_else(|e| {
+            fail!("{prog_name:?}: cannot create child cgroup {cgroup_relative_path:?}: {e}")
+        });
+    child
+        .make_child(Path::new("@inner.service"))
+        .unwrap_or_else(|e| {
+            fail!("{prog_name:?}: cannot create child cgroup {cgroup_relative_path:?}/@inner.service: {e}")
+        });
+    let child = child
+        .open_cgroup_at(Path::new("@inner.service"))
+        .unwrap_or_else(|e| {
+            fail!("{prog_name:?}: cannot open child cgroup {cgroup_relative_path:?}/@inner.service: {e}")
+        });
+
+    // SAFETY: safe FFI call
+    let pid = unsafe { libc::getpid() };
+    write_cgroup_value(&prog_name, &child, "cgroup.procs", &pid.to_string());
+
+    let e = std::process::Command::new(&program_name).args(args).exec();
+    fail!(
+        "{prog_name:?}: cannot spawn child {:?}: {}",
+        program_name,
+        e
+    );
+}
+
+fn write_cgroup_value(prog_name: &OsStr, child: &cgroup::LeafCgroup, name: &str, value: &str) {
+    let path = Path::new(name);
+    let fd = rustix::fs::openat2(
+        child.as_fd(),
+        path,
+        OFlags::NOATIME | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::WRONLY,
+        Mode::empty(),
+        ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+    )
+    .unwrap_or_else(|e| fail!("{prog_name:?}: cannot open {:?}: {}", path, e));
+    let () = File::from(fd)
+        .write_all(value.as_bytes())
+        .unwrap_or_else(|e| {
+            fail!(
+                "{prog_name:?}: Cannot write {:?} to {:?}: {}",
+                name,
+                path,
+                e
+            )
+        });
+}
+
+fn purge_cgroup(prog_name: &OsStr, cgroup: &cgroup::LeafCgroup, cgroup_relative_path: &Path) {
+    let child = match cgroup.open_cgroup_at(cgroup_relative_path) {
+        Ok(child_cgroup) => child_cgroup,
+        Err(Errno::NOENT) => return,
+        Err(other) => {
+            fail!("{prog_name:?}: cannot open child cgroup {cgroup_relative_path:?}: {other}")
+        }
+    };
+    child.kill_processes().unwrap_or_else(|e| {
+        fail!("{prog_name:?}: cannot kill programs in {cgroup_relative_path:?}: {e}")
+    });
+
+    cgroup
+        .delete_child(cgroup_relative_path)
+        .unwrap_or_else(|e| {
+            fail!("{prog_name:?}: delete child cgroup {cgroup_relative_path:?}: {e}")
+        });
+}

---
base-commit: 36ea9d85403a29f1e700f534020cc4592d78ee50
change-id: 20260528-cgroups-d609e270e649

-- 
Sincerely,
Demi Marie Obenour (she/her/hers)


  reply	other threads:[~2026-06-20 17:32 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-20 14:23 [PATCH] Set up control groups for most services Demi Marie Obenour
2026-06-20 17:27 ` Demi Marie Obenour [this message]
2026-06-24 12:13   ` [PATCH v2] " Alyssa Ross
2026-06-24 12:36     ` Alyssa Ross
2026-06-25  2:03     ` Demi Marie Obenour
2026-06-25  3:03       ` Demi Marie Obenour
2026-06-25  9:55         ` Alyssa Ross
2026-06-25  9:49       ` Alyssa Ross

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260620-cgroups-v2-1-ccae224b6c85@gmail.com \
    --to=demiobenour@gmail.com \
    --cc=devel@spectrum-os.org \
    --cc=hi@alyssa.is \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).