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] Set up control groups for most services
Date: Sat, 20 Jun 2026 10:23:09 -0400 [thread overview]
Message-ID: <20260620-cgroups-v1-1-0e5abf35101b@gmail.com> (raw)
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>
---
.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 | 13 +-
.../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 | 2 +-
host/rootfs/image/etc/s6-rc/weston/finish | 5 +
host/rootfs/image/etc/s6-rc/weston/run | 2 +-
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 | 217 ++++++++++++++++++
tools/cgroup-setup/src/main.rs | 253 +++++++++++++++++++++
42 files changed, 687 insertions(+), 16 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..6ee682c37219e504e3d9d0e9081ec67e85ad9f5f
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/serial-getty-generator/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2024-2025 Alyssa Ross <hi@alyssa.is>
+
+cgroup-setup -- serial-getty-generator
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..ed324e5a0871e6d019bc925a91f8aa8ab1aa6a18
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vm-services/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
+
+cgroup-setup -- vm-services-template
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..0299fd3705aaf5950e2ac0f686d3d211a74c9fc2
--- /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 -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2024-2025 Alyssa Ross <hi@alyssa.is>
+
+cgroup-setup -- dbus-daemon
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..1a9b10892805c0350ce12de93e156b2a338ffd24 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 {
@@ -16,10 +17,14 @@ redirfd -r 0 /dev/null
s6-envuidgid fs
s6-applyuidgid -Uzu 0
-getcwd -E dir
+getcwd dir
+multisubstitute {
+ define VM_ ${VM}
+ importas -iS dir
+}
nsenter --preserve-credentials -S0
- --mount=/run/vm/by-id/${VM}/ns/mnt
- --user=/run/vm/by-id/${VM}/ns/user
+ --mount=/run/vm/by-id/${VM_}/ns/mnt
+ --user=/run/vm/by-id/${VM_}/ns/user
unshare --cgroup --ipc --net --uts
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..78d1229247ea2a457833e323a9428e04f9a1ed3c
--- /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 -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- spectrum-router
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..4a6ca0452c437e9312964164b75843e28734ae18
--- /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 -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- vhost-user-fs
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..c1cbc523415b573c4205d5e77d7d5cc29ff51481
--- /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 -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- crosvm
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..d0cd4081fee3df4d898ef1e4803c6c8fa3c3ed45
--- /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 -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- xdg-desktop-portal-spectrum-host
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..da3fc34f057ce5788ddf3ff172f122cf53f8b705
--- /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 -- vm-services@${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..51d7d2a7f92b98761f7b836bf9ee4ebdfab41cc7
--- /dev/null
+++ b/host/rootfs/image/etc/s6-linux-init/run-image/service/vmm/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
+
+cgroup-setup -- vmm
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..73e4c0315d345a443f844244c84b8546f076771c
--- /dev/null
+++ b/host/rootfs/image/etc/s6-rc/systemd-udevd/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- systemd-udevd
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..69c67dd9620e557391076d5505fcdc8bdbddac67
--- a/host/rootfs/image/etc/s6-rc/systemd-udevd/run
+++ b/host/rootfs/image/etc/s6-rc/systemd-udevd/run
@@ -1,7 +1,7 @@
#!/bin/execlineb -WP
# 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 -- systemd-udevd
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..a37813bddc0c599eb44ab9901e1685be45cba207
--- /dev/null
+++ b/host/rootfs/image/etc/s6-rc/weston/finish
@@ -0,0 +1,5 @@
+#!/bin/execlineb -WP
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+cgroup-setup -- weston
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..5a6e64d41d6e4f2714949f13bad3019479560e04
--- a/host/rootfs/image/etc/s6-rc/weston/run
+++ b/host/rootfs/image/etc/s6-rc/weston/run
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: EUPL-1.2+
# SPDX-FileCopyrightText: 2021, 2025 Alyssa Ross <hi@alyssa.is>
+cgroup-setup -- weston
importas -Siu WAYLAND_DISPLAY
piperw 4 3
@@ -40,7 +41,6 @@ redirfd -r 0 /dev/tty1
importas -i home HOME
cd $home
-if { udevadm wait /dev/dri/card0 }
s6-setuidgid wayland
bwrap
# no --unshare-net, breaks udev hotplug
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..fd7246cd0bc2e9a980898eae42531fef601f524b
--- /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..3cc6627e01f9bf01118dcfda849d431c128f5017
--- /dev/null
+++ b/tools/cgroup-setup/src/cgroup.rs
@@ -0,0 +1,217 @@
+// 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,
+}
+
+mod epoll {
+ use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
+
+ #[expect(dead_code)]
+ pub(super) struct Epoll(OwnedFd, OwnedFd);
+
+ impl Epoll {
+ #[expect(dead_code)]
+ pub(super) fn new(other_fd: OwnedFd) -> Self {
+ // SAFETY: FFI call with valid arguments
+ let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
+ if fd == -1 {
+ panic!("epoll_create1 failed: {}", std::io::Error::last_os_error());
+ }
+
+ let mut event = libc::epoll_event {
+ events: (libc::EPOLLIN | libc::EPOLLPRI | libc::EPOLLRDHUP) as _,
+ r#u64: 0,
+ };
+
+ let res = unsafe {
+ libc::epoll_ctl(
+ fd.as_raw_fd(),
+ libc::EPOLL_CTL_ADD,
+ other_fd.as_raw_fd(),
+ &raw mut event,
+ )
+ };
+ assert_eq!(res, 0, "kernel out of memory?");
+
+ // SAFETY: epoll_create1 returns valid FD or -1 and we checked the error case
+ Self(unsafe { OwnedFd::from_raw_fd(fd) }, other_fd)
+ }
+ }
+}
+
+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..f10eadb013b987d33c741f327e5c180164824801
--- /dev/null
+++ b/tools/cgroup-setup/src/main.rs
@@ -0,0 +1,253 @@
+// 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 parse_string(original: &str, msg: &str, num: usize) -> u64 {
+ let (to_parse, scale, unit): (_, u64, &'static str) = match original.as_bytes() {
+ [rest @ .., b'K', b'B'] => (rest, 1_000, "KB"),
+ [rest @ .., b'M', b'B'] => (rest, 1_000_000, "MB"),
+ [rest @ .., b'G', b'B'] => (rest, 1_000_000_000, "GB"),
+ [rest @ .., b'T', b'B'] => (rest, 1_000_000_000_000, "TB"),
+ [rest @ .., b'P', b'B'] => (rest, 1_000_000_000_000_000, "PB"),
+ [rest @ .., b'E', b'B'] => (rest, 1_000_000_000_000_000_000, "EB"),
+ [rest @ .., b'K', b'i', b'B'] => (rest, 1 << 10, "KiB"),
+ [rest @ .., b'M', b'i', b'B'] => (rest, 1 << 20, "MiB"),
+ [rest @ .., b'G', b'i', b'B'] => (rest, 1 << 30, "GiB"),
+ [rest @ .., b'T', b'i', b'B'] => (rest, 1 << 40, "TiB"),
+ [rest @ .., b'P', b'i', b'B'] => (rest, 1 << 50, "PiB"),
+ [rest @ .., b'E', b'i', b'B'] => (rest, 1 << 60, "EiB"),
+ [rest @ .., b'B'] if rest.last().map(u8::is_ascii_digit).unwrap_or(false) => (rest, 1, "B"),
+ _ => fail!("{msg}: {original:?} is missing a unit suffix"),
+ };
+ let (to_parse, radix) = match to_parse {
+ [b'0', b'x' | b'X', s @ ..] => (s, 16),
+ _ => (to_parse, 10),
+ };
+ // SAFETY: lopping ASCII bytes off start
+ // and end of UTF-8 string leaves valid UTF_8
+ let result: u64 =
+ match u64::from_str_radix(unsafe { str::from_utf8_unchecked(to_parse) }, radix) {
+ Ok(e) => e,
+ Err(e) => {
+ fail!("{msg}: argument {num}: Cannot parse as u64: {e}")
+ }
+ };
+ if to_parse[0] == b'0' || to_parse[0] == b'+' {
+ fail!(
+ "{msg}: argument {num}: Leading {} in {original} not allowed",
+ to_parse[0] as char
+ )
+ }
+ result.checked_mul(scale).unwrap_or_else(|| {
+ fail!(
+ "{msg}: {original} is too large for unit {unit} ({original} × {scale} = {} > {})",
+ u128::from(result) * u128::from(scale),
+ u64::MAX,
+ )
+ })
+}
+
+fn main() {
+ let mut args = std::env::args_os();
+ if args.next().is_none() {
+ fail!("No command line arguments (argv[0] is NULL)");
+ }
+ let mut args = args.enumerate().map(|(num, arg)| {
+ match str::from_utf8(<OsStr as OsStrExt>::as_bytes(&arg)) {
+ Ok(v) => (num, v.to_owned()),
+ Err(e) => {
+ fail!(
+ "Argument {num} bytes {} through {} are invalid UTF-8",
+ e.valid_up_to(),
+ e.error_len().unwrap_or_else(|| arg.len())
+ )
+ }
+ }
+ });
+ let mut finished = false;
+ let mut positional_arguments;
+ let mut values = [
+ ("pids.max", None),
+ ("memory.high", None),
+ ("memory.max", None),
+ ];
+
+ let last = loop {
+ let Some((num, arg_str)) = args.next() else {
+ finished = true;
+ break None;
+ };
+ if arg_str.as_bytes().get(0) != Some(&b'-') {
+ break Some(arg_str.to_owned());
+ }
+ let Some(suffix) = arg_str.strip_prefix("--") else {
+ fail!("Short options (with a single '-', as in {arg_str:?}) are not supported")
+ };
+
+ if suffix.is_empty() {
+ break None;
+ }
+
+ for value in &mut values {
+ if let Some(suffix) = suffix.strip_prefix(value.0)
+ && let Some(suffix) = suffix.strip_prefix("=")
+ {
+ value.1 = Some(parse_string(suffix, &value.0, num));
+ }
+ }
+
+ fail!("Unknown option {arg_str:?}")
+ };
+
+ if finished {
+ positional_arguments = vec![]
+ } else {
+ positional_arguments = last.into_iter().chain(args.map(|a| a.1)).collect()
+ }
+
+ if positional_arguments.is_empty() {
+ fail!(
+ "Have {} arguments, expect at least 1",
+ positional_arguments.len()
+ )
+ }
+
+ // slow but we do not care
+ let mut child_path = positional_arguments.remove(0);
+ if child_path.len() > 247 {
+ fail!("Cgroup name {child_path:?} too long");
+ }
+ if child_path.as_bytes().contains(&b'/') {
+ fail!("Cgroup name {child_path:?} contains /");
+ }
+ child_path += ".service";
+ let child_path = Path::new(&child_path);
+
+ let local_cgroup =
+ std::fs::read("/proc/thread-self/cgroup").expect("Should be able to read from procfs");
+ if !local_cgroup.starts_with(b"0::/")
+ || !local_cgroup.ends_with(b"\n")
+ || local_cgroup.contains(&b'\0')
+ {
+ fail!("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,
+ )
+ .expect("Cannot get cgroup root");
+
+ let cgroup = match cgroup::LeafCgroup::open_cgroup(cgroup_root.as_fd(), &local_cgroup) {
+ Ok(e) => e,
+ Err(e) => fail!(
+ "Bad cgroup operation {e:?} opening {}",
+ local_cgroup.display()
+ ),
+ };
+
+ rustix::fs::flock(cgroup.as_fd(), FlockOperation::LockExclusive).expect("oops");
+ purge_cgroup(&cgroup, child_path);
+ if positional_arguments.is_empty() {
+ return;
+ }
+ if let Err(e) = cgroup.make_child(child_path) {
+ fail!("Cannot create child cgroup: {e}")
+ }
+ let child = cgroup
+ .open_cgroup_at(Path::new(&child_path))
+ .expect("cannot open child cgroup");
+ child
+ .make_child(Path::new("@inner.service"))
+ .expect("cannot make child cgroup");
+ let child = child
+ .open_cgroup_at(Path::new("@inner.service"))
+ .expect("cannot open child cgroup");
+
+ for (name, value) in values {
+ if let Some(value) = value {
+ write_cgroup_value(&child, name, &value.to_string());
+ }
+ }
+
+ // SAFETY: safe FFI call
+ let pid = unsafe { libc::getpid() };
+ write_cgroup_value(&child, "cgroup.procs", &pid.to_string());
+
+ let program = positional_arguments.remove(0);
+ let e = std::process::Command::new(program.clone())
+ .args(positional_arguments)
+ .exec();
+ fail!("Cannot spawn child {:?}: {}", program, e);
+}
+
+fn write_cgroup_value(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!("Cannot open {:?}: {}", path, e));
+ let () = File::from(fd)
+ .write_all(value.as_bytes())
+ .unwrap_or_else(|e| fail!("Cannot write {:?} to {:?}: {}", name, path, e));
+}
+
+fn purge_cgroup(cgroup: &cgroup::LeafCgroup, child_path: &Path) {
+ let child = match cgroup.open_cgroup_at(child_path) {
+ Ok(child_cgroup) => child_cgroup,
+ Err(Errno::NOENT) => return,
+ Err(other) => fail!("Cannot open child cgroup {child_path:?}: {other}"),
+ };
+ child.kill_processes().expect("cannot kill children");
+ cgroup
+ .delete_child(child_path)
+ .expect("cannot delete child");
+}
---
base-commit: 36ea9d85403a29f1e700f534020cc4592d78ee50
change-id: 20260528-cgroups-d609e270e649
--
Sincerely,
Demi Marie Obenour (she/her/hers)
next reply other threads:[~2026-06-20 14:31 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-20 14:23 Demi Marie Obenour [this message]
2026-06-20 17:27 ` [PATCH v2] Set up control groups for most services Demi Marie Obenour
2026-06-24 12:13 ` 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-v1-1-0e5abf35101b@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).