From: Demi Marie Obenour <demiobenour@gmail.com>
To: Spectrum OS Development <devel@spectrum-os.org>
Cc: Demi Marie Obenour <demiobenour@gmail.com>, Alyssa Ross <hi@alyssa.is>
Subject: [PATCH v6 02/19] tools: Add control group manager
Date: Wed, 05 Aug 2026 21:16:09 -0400 [thread overview]
Message-ID: <20260805-cgroups-v6-2-086c0f00f55f@gmail.com> (raw)
In-Reply-To: <20260805-cgroups-v6-0-086c0f00f55f@gmail.com>
The cgroup-setup Rust program can create and purge cgroups. It can also
wait for one to become empty, spawn a program in a cgroup, and more. In
the future, it will also support cgroup-based resource control. Locking
is used to ensure that concurrent invocations are safe.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
.codespellrc | 2 +-
host/rootfs/default.nix | 6 +-
pkgs/default.nix | 1 +
tools/cgroup-setup/Cargo.lock | 67 +++++++++
tools/cgroup-setup/Cargo.lock.license | 2 +
tools/cgroup-setup/Cargo.toml | 10 ++
tools/cgroup-setup/default.nix | 22 +++
tools/cgroup-setup/src/cgroup.rs | 269 ++++++++++++++++++++++++++++++++++
tools/cgroup-setup/src/main.rs | 167 +++++++++++++++++++++
9 files changed, 542 insertions(+), 4 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/pkgs/default.nix b/pkgs/default.nix
index 44f7b5ff78cb6b9e755292a6a417d0b627ed3fb0..0a13393164ad5d7f752e630763f3f97166479af5 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..f80078163f4428881b86cb6fef0c90c0c1ebaa91
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.lock.license
@@ -0,0 +1,2 @@
+SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+SPDX-License-Identifier: CC0-1.0
diff --git a/tools/cgroup-setup/Cargo.toml b/tools/cgroup-setup/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..e44ef64119aefc23a3119fe0ce01566f5d7ec98c
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.toml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+# SPDX-License-Identifier: CC0-1.0
+
+[package]
+name = "cgroup-setup"
+edition = "2024"
+
+[dependencies]
+libc = "0.2.177"
+rustix = { version = "1.1.2", features = ["fs"] }
diff --git a/tools/cgroup-setup/default.nix b/tools/cgroup-setup/default.nix
new file mode 100644
index 0000000000000000000000000000000000000000..fe3a8bcd4e6118f3cbf49c2fc0e3cda01570c484
--- /dev/null
+++ b/tools/cgroup-setup/default.nix
@@ -0,0 +1,22 @@
+# 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;
+
+ postInstall = ''
+ ln -s -- cgroup-setup "$out/bin/cgroup-purge"
+ '';
+}) (_: {})
diff --git a/tools/cgroup-setup/src/cgroup.rs b/tools/cgroup-setup/src/cgroup.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c63d7e5a4aa73429578704401c58bbafc79e7c3f
--- /dev/null
+++ b/tools/cgroup-setup/src/cgroup.rs
@@ -0,0 +1,269 @@
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+// SPDX-License-Identifier: EUPL-1.2+
+
+use std::ffi::OsStr;
+use std::fs::File;
+use std::io::{Read as _, Seek as _, Write as _};
+use std::os::unix::prelude::*;
+
+use std::path::{Component, Path, PathBuf};
+
+use rustix::fs::{AtFlags, CWD, Dir, FlockOperation};
+use rustix::path;
+use rustix::{
+ fs::{Mode, OFlags, ResolveFlags},
+ io::Errno,
+};
+
+pub enum OpenFlags {
+ Read,
+ Write,
+ Directory,
+}
+
+#[derive(Debug)]
+pub(crate) struct Cgroup {
+ fd: Vec<OwnedFd>,
+}
+
+impl AsFd for Cgroup {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.fd.last().unwrap().as_fd()
+ }
+}
+
+fn assert_single_component(component: &Path) {
+ match component.as_os_str().as_bytes() {
+ b"" | b"." | b".." => panic!("bad component"),
+ c if c.contains(&b'\0') => panic!("NUL in component"),
+ c if c.contains(&b'/') => panic!("/ in component"),
+ _ => {}
+ }
+}
+
+// Wrapper around openat2() with better defaults.
+pub fn openat2_simple(
+ fd: impl AsFd,
+ path: impl path::Arg,
+ flags: OpenFlags,
+) -> Result<OwnedFd, Errno> {
+ rustix::fs::openat2(
+ fd.as_fd(),
+ path,
+ OFlags::CLOEXEC
+ | match flags {
+ OpenFlags::Read => OFlags::RDONLY | OFlags::NOCTTY,
+ OpenFlags::Write => OFlags::WRONLY | OFlags::NOCTTY,
+ OpenFlags::Directory => OFlags::RDONLY | OFlags::DIRECTORY,
+ },
+ Mode::empty(),
+ ResolveFlags::NO_SYMLINKS | ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_XDEV,
+ )
+}
+
+pub const DEFAULT_LEAF: &str = "$inner.service";
+
+pub fn check_path(path: &Path) -> Result<(), String> {
+ let bytes = path.as_os_str().as_bytes();
+ // Path::components() skips ., so use string manipulation instead.
+ for component in bytes[path.is_absolute() as usize..].split(|&b| b == b'/') {
+ if matches!(component, b"" | b"." | b"..") {
+ return Err(format!("cgroup path {path:?} isn't canonical"));
+ }
+ }
+ Ok(())
+}
+
+// Remove all subdirectories of the given directory recursively,
+// but not the directory itself. The directory file descriptor
+// is closed.
+//
+// This isn't the most efficient possible algorithm, but
+// simplicity is more important than performance in this
+// case. Also, it keeps open more file descriptors than
+// strictly necessary, but Spectrum runs with a very high
+// limit for the number of open file descriptors, and it
+// uses shallow control group hierarchies.
+//
+// This uses a recursive algorith, but so does std::fs::remove_dir_all().
+// Trying to be more robust than the standard library is not worthwhile.
+// In particular, the standard library function must be safe on systems
+// where untrusted users (or even network endpoints!) can create deeply
+// nested directory trees, whereas in Spectrum cgroups are only writeable
+// by root.
+fn remove_recursively(mut dirfd: Dir, remaining_depth: usize) -> Result<(), Errno> {
+ if remaining_depth < 1 {
+ panic!("control groups too deeply nested");
+ }
+ while let Some(element) = dirfd.next() {
+ let parent_fd = dirfd.fd().unwrap();
+ let element = element.expect("Iterating through a cgroup directory failed?");
+ let path = element.file_name();
+ if element.file_type() != rustix::fs::FileType::Directory || path == c"." || path == c".." {
+ continue;
+ }
+ let fd = openat2_simple(parent_fd, path, OpenFlags::Directory)?;
+ remove_recursively(Dir::new(fd).unwrap(), remaining_depth - 1)?;
+ match rustix::fs::unlinkat(parent_fd, path, AtFlags::REMOVEDIR) {
+ Err(Errno::NOTEMPTY | Errno::BUSY | Errno::NOENT) | Ok(()) => {}
+ bad => return bad,
+ }
+ }
+ Ok(())
+}
+
+// If the path is absolute, make it relative.
+// Otherwise, read the current cgroup from /proc/thread-self/cgroup
+// and prepend it to the path.
+fn prepend_current_cgroup_if_needed(path: &Path) -> PathBuf {
+ if let Ok(suffix) = path.strip_prefix("/") {
+ suffix.to_owned()
+ } else {
+ // /proc/thread-self is the same as /proc/self, except for the current
+ // thread instead of the initial thread. In this case, the two are
+ // identical, but using /proc/thread-self is better practice as it is
+ // correct in more cases. Reading /proc/thread-self/cgroup should
+ // never fail unless the system is seriously broken.
+ let current_cgroup = std::fs::read("/proc/thread-self/cgroup")
+ .expect("cannot read /proc/thread-self/cgroup");
+ // Using this on a system without cgroups v2 mounted is user error
+ // and not supported.
+ let current_cgroup = current_cgroup
+ .strip_prefix(b"0::/")
+ .and_then(|e| e.strip_suffix(b"\n"))
+ .expect("you don't have cgroups v2 mounted");
+ let mut current_cgroup = PathBuf::from(OsStr::from_bytes(current_cgroup));
+ // Strip the implied $inner.service suffix.
+ // This is used to satisfy the "no internal processes" rule.
+ if current_cgroup.ends_with(Path::new(DEFAULT_LEAF)) {
+ assert!(current_cgroup.pop());
+ }
+ current_cgroup.push(path);
+ current_cgroup
+ }
+}
+
+pub(crate) fn write_value(fd: &dyn AsFd, name: &Path, value: &[u8]) -> Result<(), String> {
+ let fd = openat2_simple(fd, name, OpenFlags::Write)
+ .map_err(|e| format!("Cannot open {name:?}: {e}"))?;
+ File::from(fd).write_all(value).map_err(|e| {
+ format!(
+ "Cannot write {:?} to {name:?}: {e}",
+ OsStr::from_bytes(value)
+ )
+ })
+}
+
+impl Cgroup {
+ pub fn new(path: &Path) -> Result<Self, String> {
+ let cgroup_root = rustix::fs::openat2(
+ CWD,
+ Path::new("/sys/fs/cgroup"),
+ OFlags::CLOEXEC | OFlags::DIRECTORY | OFlags::RDONLY,
+ Mode::empty(),
+ ResolveFlags::NO_SYMLINKS | ResolveFlags::NO_MAGICLINKS,
+ )
+ .map_err(|e| format!("Cannot open /sys/fs/cgroup: {e}"))?;
+ let mut cgroup = Self {
+ fd: vec![(cgroup_root)],
+ };
+
+ let path = prepend_current_cgroup_if_needed(path);
+ for component in path.components() {
+ let Component::Normal(component) = component else {
+ unreachable!()
+ };
+ let sub_fd = openat2_simple(&cgroup, component, OpenFlags::Directory)
+ .map_err(|e| format!("Cannot open sub-cgroup {component:?}: {e}"))?;
+ // Take a shared lock on the cgroup.
+ rustix::fs::flock(&sub_fd, FlockOperation::LockShared)
+ .map_err(|e| format!("Cannot lock sub-cgroup {component:?}: {e}"))?;
+ cgroup.fd.push(sub_fd);
+ }
+ Ok(cgroup)
+ }
+
+ pub fn wait_for_empty(fd: &dyn AsFd) -> std::io::Result<()> {
+ let wait_file = openat2_simple(fd, c"cgroup.events", OpenFlags::Read)?;
+ let mut wait_fd = File::from(wait_file);
+ let mut v = vec![];
+ loop {
+ v.clear();
+ 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");
+ // Check that the cgroup isn't already empty. If it was,
+ // the kernel would not send an event and poll() would wait
+ // forever.
+ if v.split(|&c| c == b'\n').any(|line| line == b"populated 0") {
+ break;
+ }
+ let mut fds = libc::pollfd {
+ fd: wait_fd.as_raw_fd(),
+ events: libc::POLLPRI | libc::POLLERR,
+ revents: 0,
+ };
+ // SAFETY: FFI call, valid arguments, fds contains 1 element
+ if unsafe { libc::poll(&raw mut fds, 1, -1) } != 1 {
+ panic!("poll failed");
+ }
+ }
+ drop(wait_fd);
+ Ok(())
+ }
+
+ pub fn purge_child(&mut self, path: &Path) -> Result<(), String> {
+ assert_single_component(path);
+ // See if we can just delete the child directly.
+ match rustix::fs::unlinkat(&self, Path::new(path), AtFlags::REMOVEDIR) {
+ // If the cgroup was successfully deleted, or if it
+ // has already been deleted, we are done.
+ Ok(()) | Err(Errno::NOENT) => return Ok(()),
+ // If this cgroup is in use, keep going.
+ Err(Errno::BUSY) => {}
+ Err(e) => return Err(format!("Cannot purge {path:?}: {e}")),
+ }
+
+ let sub_fd = match openat2_simple(&self, path, OpenFlags::Directory) {
+ Ok(sub_fd) => sub_fd,
+ Err(Errno::NOENT) => return Ok(()),
+ Err(e) => {
+ return Err(format!("Cannot open sub-cgroup {path:?}: {e}",));
+ }
+ };
+
+ // Take an exclusive lock on the cgroup that is about to be
+ // removed. This avoids concurrent executions of this program
+ // operating on deleted sub-cgroups.
+ rustix::fs::flock(&sub_fd, FlockOperation::LockExclusive)
+ .map_err(|e| format!("Cannot lock sub-cgroup: {e}"))?;
+
+ // Kill all processes in the child cgroup.
+ write_value(&sub_fd, Path::new("cgroup.kill"), b"1")?;
+
+ // Wait for the child cgroup to become empty.
+ Self::wait_for_empty(&sub_fd)
+ .map_err(|e| format!("Cannot wait for cgroup to become empty: {e}"))
+ .inspect_err(|_| {
+ self.fd.pop().unwrap();
+ })?;
+
+ // Remove the child cgroup and its contents recursively.
+ remove_recursively(Dir::new(sub_fd).unwrap(), 1000)
+ .map_err(|e| format!("Cannot remove: {e}"))?;
+
+ // Delete the cgroup. If it's been re-created in the meantime
+ // and is currently in use, this is not an error. Another
+ // process deleting the cgroup is also not an error. Both of
+ // these can happen because of the time period between
+ // remove_child_directories() closing the file descriptor
+ // (releasing its lock) and the above call to flock().
+ match rustix::fs::unlinkat(&self, path, AtFlags::REMOVEDIR) {
+ Ok(()) | Err(Errno::BUSY) | Err(Errno::NOENT) => Ok(()),
+ Err(e) => Err(format!("Cannot delete: {e}")),
+ }
+ }
+}
diff --git a/tools/cgroup-setup/src/main.rs b/tools/cgroup-setup/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..58ca7295bb348a2c92640a68c60b285b2d7a1494
--- /dev/null
+++ b/tools/cgroup-setup/src/main.rs
@@ -0,0 +1,167 @@
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+// SPDX-License-Identifier: EUPL-1.2+
+
+mod cgroup;
+
+use cgroup::{Cgroup, OpenFlags, openat2_simple, write_value};
+use rustix::{
+ fs::{FlockOperation, Mode, XattrFlags},
+ io::Errno,
+};
+use std::{
+ env::ArgsOs,
+ fs::File,
+ io::Read as _,
+ os::unix::prelude::*,
+ path::{Path, PathBuf},
+};
+
+// Check that the path is canonical,
+// then split it into basename and filename.
+fn split_path(path: &Path) -> Result<(&Path, &Path), String> {
+ cgroup::check_path(path)?;
+ Ok((path.parent().unwrap(), Path::new(path.file_name().unwrap())))
+}
+
+fn enable_subtree_control(fd: &dyn AsFd) -> Result<(), String> {
+ let mut buf = Vec::new();
+ File::from(
+ openat2_simple(fd, c"cgroup.controllers", OpenFlags::Read)
+ .map_err(|e| format!("Cannot open cgroup.controllers: {e}"))?,
+ )
+ .read_to_end(&mut buf)
+ .map_err(|e| format!("Cannot read cgroup.controllers: {e}"))?;
+ let mut subtree = vec![];
+ for controller in buf.split(|&b| b == b' ') {
+ if !subtree.is_empty() {
+ subtree.push(b' ');
+ }
+ subtree.push(b'+');
+ subtree.extend_from_slice(controller);
+ }
+ if !subtree.is_empty() {
+ write_value(&fd, Path::new("cgroup.subtree_control"), &subtree)?;
+ }
+ Ok(())
+}
+
+fn cgroup_setup(args: ArgsOs) -> Result<(), String> {
+ let mut leaf = false;
+ let mut systemd_delegate = false;
+ let mut wait = true;
+ let mut args = args.peekable();
+ while let Some(arg) = args.peek() {
+ if !arg.as_bytes().starts_with(b"-") {
+ break;
+ }
+ let arg = args.next().unwrap();
+ let Some(arg_) = arg.as_bytes().strip_prefix(b"--") else {
+ return Err("takes no short options".to_owned());
+ };
+ match arg_ {
+ b"" => break,
+ b"leaf" => leaf = true,
+ b"no-wait" => wait = false,
+ b"systemd-delegate" => systemd_delegate = true,
+ _ => return Err(format!("unknown long option {arg:?}")),
+ }
+ }
+ let Some(cgroup_path) = args.next().map(PathBuf::from) else {
+ return Err("have no positional arguments, expected at least 1".to_owned());
+ };
+
+ let (parent_cgroup_path, child_cgroup_path) = split_path(&cgroup_path)?;
+ let cgroup = Cgroup::new(parent_cgroup_path)?;
+ match rustix::fs::mkdirat(&cgroup, child_cgroup_path, Mode::from_raw_mode(0o755)) {
+ Ok(()) | Err(Errno::EXIST) => {}
+ Err(e) => {
+ return Err(format!(
+ "Cannot make child cgroup {child_cgroup_path:?}: {e}"
+ ));
+ }
+ }
+ let child = openat2_simple(&cgroup, child_cgroup_path, OpenFlags::Directory)
+ .map_err(|e| format!("Cannot make child cgroup: {e}"))?;
+ // While waiting, hold an exclusive lock on the child.
+ // This avoids two processes both waiting for the same cgroup to become
+ // empty, then spawning processes in the same cgroup.
+ rustix::fs::flock(&child, FlockOperation::LockExclusive)
+ .map_err(|e| format!("Cannot take an exclusive lock on child cgroup: {e}"))?;
+ if wait {
+ Cgroup::wait_for_empty(&child)
+ .map_err(|e| format!("Cannot wait for {parent_cgroup_path:?} to be empty: {e}"))?;
+ }
+ let pid = std::process::id().to_string();
+ if leaf {
+ if args.len() != 0 {
+ // If we aren't delegating any cgroups, don't create a sub-cgroup.
+ write_value(&child, Path::new("cgroup.procs"), pid.as_bytes())
+ .map_err(|e| format!("Cannot move process to child cgroup: {e}"))?;
+ }
+ } else {
+ // If the child process will need to manage cgroups itself, it will need
+ // to set up a sub-cgroup due to the "no internal processes" rule. It's
+ // simplest to just do it automatically. If the cgroup already exists,
+ // that isn't an error.
+ match rustix::fs::mkdirat(&child, cgroup::DEFAULT_LEAF, Mode::from_raw_mode(0o755)) {
+ Ok(()) | Err(Errno::EXIST) => {}
+ Err(e) => return Err(format!("Cannot make child cgroup: {e}")),
+ }
+ if args.len() != 0 {
+ let child_proc_path = Path::new(cgroup::DEFAULT_LEAF).join(Path::new("cgroup.procs"));
+ write_value(&child, &child_proc_path, pid.as_bytes())
+ .map_err(|e| format!("Cannot move process to child cgroup: {e}"))?;
+ }
+ if systemd_delegate {
+ // systemd-aware programs expect to have user.delegate=1
+ // and to set cgroup.subtree_control themselves
+ rustix::fs::fsetxattr(&child, c"user.delegate", b"1", XattrFlags::empty()).map_err(
+ |e| format!("Cannot enable cgroup delegation in {parent_cgroup_path:?}: {e}"),
+ )?
+ } else {
+ // Spectrum's programs do not check for user.delegate=1
+ // and expect the caller to set cgroup.subtree_control.
+ enable_subtree_control(&child)?;
+ }
+ }
+ let Some(program_name) = args.next() else {
+ return Ok(());
+ };
+ let e = std::process::Command::new(&program_name).args(args).exec();
+ Err(format!("Cannot spawn child {program_name:?}: {e}",))
+}
+
+fn cgroup_purge(mut args: ArgsOs) -> Result<(), String> {
+ if args.len() != 1 {
+ return Err("usage: cgroup-purge CGROUP_TO_PURGE".to_owned());
+ }
+ let arg = args.next().unwrap();
+ let (parent, child) = split_path(Path::new(&arg))?;
+ Cgroup::new(parent)?.purge_child(child)
+}
+
+fn run(prog_name: &Path, args: ArgsOs) -> Result<(), String> {
+ match prog_name.file_name().map(|f| f.as_bytes()) {
+ Some(b"cgroup-setup") => cgroup_setup(args),
+ Some(b"cgroup-purge") => cgroup_purge(args),
+ _ => Err(format!(
+ "must be invoked as \"cgroup-setup\" or \
+ \"cgroup-purge\", got {prog_name:?}",
+ )),
+ }
+}
+
+fn main() {
+ let mut args = std::env::args_os();
+ let Some(prog_name) = args.next() else {
+ eprintln!("No command line arguments (argv[0] is NULL)");
+ std::process::exit(1);
+ };
+ match run(Path::new(&prog_name), args) {
+ Ok(()) => {}
+ Err(e) => {
+ eprintln!("{prog_name:?}: {}", e);
+ std::process::exit(1);
+ }
+ }
+}
--
2.55.0
next prev parent reply other threads:[~2026-08-06 1:19 UTC|newest]
Thread overview: 132+ 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 ` [PATCH v2] " 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
2026-07-11 20:12 ` [PATCH v3 00/22] Control group support Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 01/22] host/rootfs: Mount filesystems before s6-rc-init Demi Marie Obenour
2026-07-13 9:39 ` Alyssa Ross
2026-07-13 17:27 ` Demi Marie Obenour
2026-07-15 18:28 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 00/20] Control group support Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 01/20] host/rootfs: Mount filesystems before s6-rc-init Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 02/20] tools: Add control group manager Demi Marie Obenour
2026-07-22 16:01 ` Alyssa Ross
2026-07-23 23:07 ` Demi Marie Obenour
2026-07-27 12:10 ` Alyssa Ross
2026-07-30 0:40 ` Demi Marie Obenour
2026-07-30 14:53 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 03/20] Documentation: Mention control groups Demi Marie Obenour
2026-07-27 11:22 ` Alyssa Ross
2026-07-28 10:41 ` Valentin Gagarin
2026-07-22 1:59 ` [PATCH v4 04/20] Mount cgroup2 filesystem at /sys/fs/cgroup Demi Marie Obenour
2026-07-27 11:23 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 05/20] host/rootfs: Add helper program for per-VM services Demi Marie Obenour
2026-07-27 11:27 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 06/20] host/rootfs: Enable controllers in sub-cgroups Demi Marie Obenour
2026-07-27 12:11 ` Alyssa Ross
2026-07-28 2:19 ` Demi Marie Obenour
2026-07-29 14:13 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 07/20] host/rootfs: Add comments where cgroups are intentionally not used Demi Marie Obenour
2026-07-27 12:12 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 08/20] host/rootfs: serial-getty-generator: Use cgroups Demi Marie Obenour
2026-07-27 12:14 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 09/20] host/rootfs: Set up parent cgroup for all per-VM services Demi Marie Obenour
2026-07-27 12:16 ` Alyssa Ross
2026-07-28 3:01 ` Demi Marie Obenour
2026-07-29 14:29 ` Alyssa Ross
2026-07-29 20:20 ` Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 10/20] host/rootfs: Create per-VM cgroup for all of the VM's services Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 11/20] host/rootfs: run-vmm: Create per-VM cgroup Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 12/20] host/rootfs: run-appimage: Purge the " Demi Marie Obenour
2026-07-27 12:18 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 13/20] host/rootfs: run-flatpak: " Demi Marie Obenour
2026-07-27 12:19 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 14/20] host/rootfs: dbus: Run in cgroup Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 15/20] host/rootfs: vhost-user-fs: " Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 16/20] host/rootfs: vhost-user-gpu: " Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 17/20] host/rootfs: xdg-desktop-portal-spectrum-host: " Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 18/20] host/rootfs: systemd-udevd: " Demi Marie Obenour
2026-07-27 12:20 ` Alyssa Ross
2026-07-28 3:11 ` Demi Marie Obenour
2026-07-29 14:15 ` Alyssa Ross
2026-07-29 20:39 ` Demi Marie Obenour
2026-07-30 14:55 ` Alyssa Ross
2026-07-22 1:59 ` [PATCH v4 19/20] host/rootfs: weston: " Demi Marie Obenour
2026-07-27 12:23 ` Alyssa Ross
2026-07-28 3:14 ` Demi Marie Obenour
2026-07-22 1:59 ` [PATCH v4 20/20] host/rootfs: spectrum-router: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 00/19] Control group support Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 01/19] host/rootfs: Mount filesystems before s6-rc-init Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 02/19] tools: Add control group manager Demi Marie Obenour
2026-08-03 12:47 ` Alyssa Ross
2026-08-05 1:36 ` Demi Marie Obenour
2026-08-05 16:39 ` Alyssa Ross
2026-07-31 21:54 ` [PATCH v5 03/19] Documentation: Mention control groups Demi Marie Obenour
2026-08-03 13:22 ` Alyssa Ross
2026-07-31 21:54 ` [PATCH v5 04/19] Mount cgroup2 filesystem at /sys/fs/cgroup Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 05/19] host/rootfs: Enable controllers in non-root cgroups Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 06/19] host/rootfs: Add comments where cgroups are intentionally not used Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 07/19] host/rootfs: serial-getty-generator: Use cgroups Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 08/19] host/rootfs: systemd-udevd: Run in cgroup Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 09/19] host/rootfs: weston: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 10/19] host/rootfs: Set up parent cgroup for all per-VM services Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 11/19] host/rootfs: Create per-VM cgroup for all of the VM's services Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 12/19] host/rootfs: run-vmm: Create per-VM cgroup Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 13/19] host/rootfs: run-appimage: Purge the " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 14/19] host/rootfs: run-flatpak: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 15/19] host/rootfs: dbus: Run in cgroup Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 16/19] host/rootfs: vhost-user-fs: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 17/19] host/rootfs: vhost-user-gpu: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 18/19] host/rootfs: xdg-desktop-portal-spectrum-host: " Demi Marie Obenour
2026-07-31 21:54 ` [PATCH v5 19/19] host/rootfs: spectrum-router: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 00/19] Control group support Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 01/19] host/rootfs: Mount filesystems before s6-rc-init Demi Marie Obenour
2026-08-06 1:16 ` Demi Marie Obenour [this message]
2026-08-06 6:58 ` [PATCH v6 02/19] tools: Add control group manager Demi Marie Obenour
2026-08-12 21:10 ` Alyssa Ross
2026-08-06 1:16 ` [PATCH v6 03/19] Documentation: Mention control groups Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 04/19] Mount cgroup2 filesystem at /sys/fs/cgroup Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 05/19] host/rootfs: Enable controllers in non-root cgroups Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 06/19] host/rootfs: Add comments where cgroups are intentionally not used Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 07/19] host/rootfs: serial-getty-generator: Use cgroups Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 08/19] host/rootfs: systemd-udevd: Run in cgroup Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 09/19] host/rootfs: weston: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 10/19] host/rootfs: Set up parent cgroup for all per-VM services Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 11/19] host/rootfs: Create per-VM cgroup for all of the VM's services Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 12/19] host/rootfs: run-vmm: Create per-VM cgroup Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 13/19] host/rootfs: run-appimage: Purge the " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 14/19] host/rootfs: run-flatpak: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 15/19] host/rootfs: dbus: Run in cgroup Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 16/19] host/rootfs: vhost-user-fs: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 17/19] host/rootfs: vhost-user-gpu: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 18/19] host/rootfs: xdg-desktop-portal-spectrum-host: " Demi Marie Obenour
2026-08-06 1:16 ` [PATCH v6 19/19] host/rootfs: spectrum-router: " Demi Marie Obenour
2026-08-12 21:06 ` [PATCH v6 00/19] Control group support Alyssa Ross
2026-07-11 20:12 ` [PATCH v3 02/22] scripts: Support symlinks in s6-rc-compile inputs Demi Marie Obenour
2026-07-13 9:42 ` Alyssa Ross
2026-07-13 14:19 ` Demi Marie Obenour
2026-07-15 18:30 ` Alyssa Ross
2026-07-11 20:12 ` [PATCH v3 03/22] tools: Add control group manager Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 04/22] Documentation: Mention control groups Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 05/22] Mount cgroup2 filesystem at /sys/fs/cgroup Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 06/22] host/rootfs: Add helper program for per-VM services Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 07/22] host/rootfs: Enable controllers in sub-cgroups Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 08/22] host/rootfs: Add comments where cgroups are intentionally not used Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 09/22] host/rootfs: serial-getty-generator: Use cgroups Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 10/22] host/rootfs: Set up parent cgroup for all per-VM services Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 11/22] host/rootfs: Create per-VM cgroup for all of the VM's services Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 12/22] host/rootfs: run-vmm: Create per-VM cgroup Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 13/22] host/rootfs: run-appimage: Purge the " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 14/22] host/rootfs: run-flatpak: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 15/22] host/rootfs: dbus: Run in cgroup Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 16/22] host/rootfs: vhost-user-fs: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 17/22] host/rootfs: vhost-user-gpu: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 18/22] host/rootfs: xdg-desktop-portal-spectrum-host: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 19/22] host/rootfs: systemd-udevd: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 20/22] host/rootfs: weston: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 21/22] host/rootfs: spectrum-router: " Demi Marie Obenour
2026-07-11 20:12 ` [PATCH v3 22/22] host/rootfs: vm-import: Use elglob -w Demi Marie Obenour
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=20260805-cgroups-v6-2-086c0f00f55f@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/doc
https://spectrum-os.org/git/mktuntap
https://spectrum-os.org/git/spectrum
https://spectrum-os.org/git/ucspi-vsock
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).