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 v4 02/20] tools: Add control group manager
Date: Tue, 21 Jul 2026 21:59:07 -0400 [thread overview]
Message-ID: <20260721-cgroups-v4-2-46b2e5fff7b6@gmail.com> (raw)
In-Reply-To: <20260721-cgroups-v4-0-46b2e5fff7b6@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.
This program can also be used in an s6 finish script. When passed the
args of such a script, it automatically purges the correct cgroup. It
also tells s6 to not restart the service if it dumped core. Core dumps
are often due to memory corruption, and automatically restarting a
service that dumped core makes memory corruption attacks easier.
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
.codespellrc | 2 +-
host/rootfs/default.nix | 6 +-
host/rootfs/file-list.mk | 2 +
host/rootfs/image/usr/bin/cgroup-purge | 1 +
host/rootfs/image/usr/bin/cgroup-s6-finish | 1 +
pkgs/default.nix | 1 +
tools/cgroup-setup/Cargo.lock | 67 ++++++
tools/cgroup-setup/Cargo.lock.license | 2 +
tools/cgroup-setup/Cargo.toml | 11 +
tools/cgroup-setup/default.nix | 18 ++
tools/cgroup-setup/src/cgroup.rs | 349 +++++++++++++++++++++++++++++
tools/cgroup-setup/src/main.rs | 347 ++++++++++++++++++++++++++++
12 files changed, 803 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/host/rootfs/file-list.mk b/host/rootfs/file-list.mk
index 3899d620717fc97f42e669e5313c4100dcf5b1cd..e1280ab56d8797e40b9b1c584ab0daef3cda41d7 100644
--- a/host/rootfs/file-list.mk
+++ b/host/rootfs/file-list.mk
@@ -79,6 +79,8 @@ LINKS = \
image/etc/s6-linux-init/run-image/service/vmm/template/run \
image/lib \
image/sbin \
+ image/usr/bin/cgroup-purge \
+ image/usr/bin/cgroup-s6-finish \
image/usr/bin/systemd-udevd
S6_RC_FILES = \
diff --git a/host/rootfs/image/usr/bin/cgroup-purge b/host/rootfs/image/usr/bin/cgroup-purge
new file mode 120000
index 0000000000000000000000000000000000000000..a0c8d8e144d72b69c613eb0613e39acc9df979df
--- /dev/null
+++ b/host/rootfs/image/usr/bin/cgroup-purge
@@ -0,0 +1 @@
+cgroup-setup
\ No newline at end of file
diff --git a/host/rootfs/image/usr/bin/cgroup-s6-finish b/host/rootfs/image/usr/bin/cgroup-s6-finish
new file mode 120000
index 0000000000000000000000000000000000000000..a0c8d8e144d72b69c613eb0613e39acc9df979df
--- /dev/null
+++ b/host/rootfs/image/usr/bin/cgroup-s6-finish
@@ -0,0 +1 @@
+cgroup-setup
\ No newline at end of file
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..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..7ed6d6a0ea3bbfc4064b9f39383d0788c4bd84e5
--- /dev/null
+++ b/tools/cgroup-setup/Cargo.toml
@@ -0,0 +1,11 @@
+# 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"] }
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..c953d26badfdac0a1e3d7057a867aec3b3247e18
--- /dev/null
+++ b/tools/cgroup-setup/src/cgroup.rs
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: EUPL-1.2+
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+use std::ffi::OsStr;
+use std::fmt::Display;
+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, FlockOperation, XattrFlags};
+use rustix::{
+ fs::{Mode, OFlags, ResolveFlags},
+ io::Errno,
+};
+
+#[derive(Debug)]
+pub(crate) struct Cgroup {
+ path: PathBuf,
+ fd: Vec<(OwnedFd, bool)>,
+}
+
+impl AsFd for Cgroup {
+ fn as_fd(&self) -> BorrowedFd<'_> {
+ self.fd.last().unwrap().0.as_fd()
+ }
+}
+
+fn assert_single_component(component: &[u8]) {
+ match component {
+ b"" | b"." | b".." => panic!("bad component"),
+ _ if component.contains(&b'\0') => panic!("NUL in component"),
+ _ if component.contains(&b'/') => panic!("/ in component"),
+ _ => {}
+ }
+}
+
+impl Cgroup {
+ pub fn new(exclusive: bool) -> Result<Self, String> {
+ 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,
+ )
+ .map_err(|e| format!("Cannot open /sys/fs/cgroup: {e}"))?;
+
+ let lock_operation = if exclusive {
+ FlockOperation::LockExclusive
+ } else {
+ FlockOperation::LockShared
+ };
+ rustix::fs::flock(cgroup_root.as_fd(), lock_operation)
+ .map_err(|e| format!("Cannot lock /sys/fs/cgroup: {e}"))?;
+ Ok(Self {
+ path: PathBuf::from("/sys/fs/cgroup"),
+ fd: vec![(cgroup_root, exclusive)],
+ })
+ }
+
+ pub fn enable_delegation(&self, depth: usize) -> Result<(), Errno> {
+ let (fd, exclusive) = &self.fd[self.fd.len() - depth];
+ assert!(exclusive);
+ rustix::fs::fsetxattr(fd.as_fd(), c"user.delegate", b"1", XattrFlags::empty())
+ }
+
+ pub fn enable_subtree_control(&self, depth: usize) -> Result<(), String> {
+ let (fd, exclusive) = &self.fd[self.fd.len() - depth];
+ assert!(exclusive);
+ let p = Path::new("cgroup.controllers");
+ let mut buf = self.read_control_file(fd.as_fd(), p)?;
+ let mut subtree = vec![];
+ if buf.ends_with(b"\n") {
+ buf.pop();
+ }
+ for controller in buf.split(|&b| b == b' ').filter(|e| !e.is_empty()) {
+ for &c in controller {
+ if c <= b' ' || c >= 0x7F {
+ return Err(format!("Bad byte {c} in cgroup.controllers"));
+ }
+ }
+ if !subtree.is_empty() {
+ subtree.push(b' ');
+ }
+ subtree.push(b'+');
+ subtree.extend_from_slice(controller);
+ }
+ if !subtree.is_empty() {
+ self.write_cgroup_value("cgroup.subtree_control", str::from_utf8(&subtree).unwrap())?;
+ }
+ Ok(())
+ }
+
+ pub fn read_control_file(&self, fd: BorrowedFd, p: &Path) -> Result<Vec<u8>, String> {
+ let mut buf = Vec::new();
+ let err = |e: &dyn Display, p: &Path, msg: &str| {
+ let path = self.path.join(p);
+ format!("Cannot {msg} {path:?}: {e}")
+ };
+ File::from(open_subtree_raw(Path::new(p), fd.as_fd()).map_err(|e| err(&e, p, "open"))?)
+ .read_to_end(&mut buf)
+ .map_err(|e| err(&e, p, "read"))?;
+ Ok(buf)
+ }
+
+ /// Open a single component as a sub-cgroup
+ fn open_sub_cgroup_raw(&self, access: OFlags, component: &[u8]) -> Result<OwnedFd, Errno> {
+ assert_single_component(component);
+ rustix::fs::openat2(
+ self.as_fd(),
+ Path::new(OsStr::from_bytes(component)),
+ OFlags::CLOEXEC | OFlags::NOFOLLOW | access,
+ Mode::empty(),
+ ResolveFlags::NO_SYMLINKS
+ | ResolveFlags::NO_MAGICLINKS
+ | ResolveFlags::BENEATH
+ | ResolveFlags::NO_XDEV,
+ )
+ }
+
+ pub fn open_sub_cgroup(
+ &mut self,
+ path: &std::path::Path,
+ exclusive: bool,
+ allow_missing: bool,
+ ) -> Result<bool, String> {
+ let mut iter = path.components().peekable();
+ while let Some(component) = iter.next() {
+ let component = match component {
+ Component::Normal(component) => component,
+ _ => unreachable!(),
+ };
+ let sub_fd = match self
+ .open_sub_cgroup_raw(OFlags::DIRECTORY | OFlags::RDONLY, component.as_bytes())
+ {
+ Ok(sub_fd) => {
+ self.path.push(component);
+ sub_fd
+ }
+ Err(Errno::NOENT) if allow_missing => return Ok(false),
+ Err(e) => {
+ return Err(format!(
+ "Cannot open sub-cgroup {component:?} of {:?}: {e}",
+ self.path
+ ));
+ }
+ };
+ let exclusive = exclusive && iter.peek().is_none();
+ let lock_operation = if exclusive {
+ FlockOperation::LockExclusive
+ } else {
+ FlockOperation::LockShared
+ };
+ rustix::fs::flock(sub_fd.as_fd(), lock_operation).map_err(|e| {
+ let msg = format!("Cannot lock sub-cgroup {:?}: {e}", self.path);
+ self.path.pop();
+ msg
+ })?;
+ self.fd.push((sub_fd, exclusive));
+ }
+ Ok(true)
+ }
+
+ pub fn open_subtree(&self, path: &std::path::Path) -> Result<OwnedFd, Errno> {
+ let dirfd = self.as_fd();
+ open_subtree_raw(path, dirfd)
+ }
+
+ fn exclusive(&self) -> bool {
+ self.fd.last().unwrap().1
+ }
+
+ pub fn joined_path(&self, p: &Path) -> PathBuf {
+ let mut owned_p = self.path.clone();
+ owned_p.push(p);
+ owned_p
+ }
+
+ pub fn wait_for_empty(&self) -> std::io::Result<()> {
+ assert!(self.exclusive());
+ let wait_file = self.open_subtree(std::path::Path::new("cgroup.events"))?;
+ let poll_fd = wait_file.as_raw_fd();
+ let mut wait_fd = File::from(wait_file);
+ let mut fds = libc::pollfd {
+ fd: poll_fd,
+ events: libc::POLLPRI | libc::POLLERR,
+ revents: 0,
+ };
+ 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");
+ if v.split(|&c| c == b'\n').any(|line| line == b"populated 0") {
+ break;
+ }
+ // SAFETY: FFI call, valid arguments, fds contains 1 element
+ if unsafe { libc::poll(&raw mut fds, 1, -1) } != 1 {
+ panic!("poll failed");
+ }
+ }
+ Ok(())
+ }
+
+ pub(crate) fn make_child(&mut self, path: &Path) -> Result<(), Errno> {
+ assert!(self.exclusive());
+ let component = path.as_os_str().as_bytes();
+ assert_single_component(component);
+ match rustix::fs::mkdirat(
+ self.as_fd(),
+ path,
+ Mode::RUSR
+ | Mode::WUSR
+ | Mode::XUSR
+ | Mode::RGRP
+ | Mode::XGRP
+ | Mode::ROTH
+ | Mode::XOTH,
+ ) {
+ Ok(()) | Err(Errno::EXIST) => {}
+ bad => return bad,
+ }
+ let p = self.open_sub_cgroup_raw(OFlags::RDONLY | OFlags::DIRECTORY, component)?;
+ // exclusive lock on parent acts as exclusive lock on child
+ self.fd.push((p, true));
+ self.path.push(path);
+ Ok(())
+ }
+
+ pub(super) fn purge(&mut self, path: &Path) -> Result<(), String> {
+ assert!(self.exclusive());
+ match rustix::fs::unlinkat(self.as_fd(), Path::new(path), AtFlags::REMOVEDIR) {
+ // Trying to purge a deleted cgroup is not an error.
+ Ok(()) | Err(Errno::NOENT) => return Ok(()),
+ Err(Errno::BUSY) => {}
+ Err(e) => return Err(format!("Cannot purge {:?}: {e}", self.joined_path(path))),
+ }
+ if !self.open_sub_cgroup(path, true, true)? {
+ return Ok(());
+ }
+
+ rustix::fs::flock(
+ self.fd[self.fd.len() - 2].0.as_fd(),
+ FlockOperation::LockShared,
+ )
+ .map_err(|e| format!("Cannot relock {:?}: {e}", self.path.parent()))?;
+ self.write_cgroup_value("cgroup.kill", "1")?;
+ self.wait_for_empty()
+ .map_err(|e| format!("Cannot wait for cgroup to become empty: {e}"))?;
+ let fd = self.fd.pop().unwrap().0;
+ let v = (|| {
+ remove_recursively(fd, 1000)
+ .map_err(|e| format!("Cannot remove {:?}: {e}", self.path))?;
+ rustix::fs::flock(self.as_fd(), FlockOperation::LockExclusive)
+ .map_err(|e| format!("Cannot lock {:?}: {e}", self.path))?;
+ match rustix::fs::unlinkat(
+ self.as_fd(),
+ Path::new(self.path.file_name().unwrap()),
+ AtFlags::REMOVEDIR,
+ ) {
+ // something might have re-created the cgroup in the meantime, which is okay
+ Ok(()) | Err(Errno::BUSY) => Ok(()),
+ Err(e) => Err(format!("Cannot lock {:?}: {e}", self.path)),
+ }
+ })();
+ assert!(self.path.pop());
+ v
+ }
+
+ pub(crate) fn write_cgroup_value(&self, name: &str, value: &str) -> Result<(), String> {
+ let path = Path::new(name);
+ let fd = rustix::fs::openat2(
+ self.as_fd(),
+ path,
+ OFlags::NOATIME | OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::WRONLY,
+ Mode::empty(),
+ ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+ )
+ .map_err(|e| format!("Cannot open {:?}: {}", self.joined_path(Path::new(name)), e))?;
+ File::from(fd).write_all(value.as_bytes()).map_err(|e| {
+ format!(
+ "Cannot write {:?} to {:?}: {}",
+ value,
+ self.joined_path(Path::new(name)),
+ e
+ )
+ })
+ }
+}
+
+fn open_subtree_raw(path: &Path, dirfd: BorrowedFd<'_>) -> Result<OwnedFd, Errno> {
+ rustix::fs::openat2(
+ dirfd,
+ path,
+ OFlags::CLOEXEC | OFlags::NOFOLLOW | OFlags::RDONLY,
+ Mode::empty(),
+ ResolveFlags::NO_SYMLINKS | ResolveFlags::BENEATH | ResolveFlags::NO_XDEV,
+ )
+}
+
+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)?;
+ }
+ drop(d);
+ 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::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..2e7a4e25213a4aa2449b292f8005e1da23cbb1f9
--- /dev/null
+++ b/tools/cgroup-setup/src/main.rs
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: EUPL-1.2+
+// SPDX-FileCopyrightText: 2026 Demi Marie Obenour <demiobenour@gmail.com>
+
+use std::{
+ ffi::{OsStr, OsString},
+ os::unix::prelude::*,
+ path::{Path, PathBuf},
+};
+
+use crate::cgroup::Cgroup;
+
+mod cgroup;
+
+fn check_path(path: &OsStr) -> Result<(), String> {
+ if path.is_empty() {
+ return Ok(());
+ }
+
+ for component in path.as_bytes().split(|&b| b == b'/') {
+ match component {
+ b"" | b"." | b".." => {
+ return Err(format!("Path {path:?} has empty, ., or .. component"));
+ }
+ // Cannot happen: command line arguments have no NUL byte,
+ // and /proc/self/cgroup having a NUL byte is a kernel bug.
+ _ if component.contains(&b'\0') => panic!("Path {path:?} has NUL byte"),
+ _ if component.len() > 255 => {
+ return Err(format!(
+ "Path {path:?} has component {:?} that is longer than 255 bytes",
+ OsStr::from_bytes(component)
+ ));
+ }
+ _ => {}
+ }
+ }
+
+ Ok(())
+}
+
+/// Get the path of the cgroup for the provided command-line argument.
+/// Returns an empty path if the path is "/", or if it is "." and the
+/// current cgroup is "/".
+///
+/// # Errors
+///
+/// Fails if the provided path is invalid or empty, or if it is relative
+/// and the local cgroup cannot be determined.
+fn get_cgroup(cgroup_path: OsString) -> Result<PathBuf, String> {
+ if cgroup_path.as_bytes().starts_with(b"/") {
+ let mut cgroup_path = cgroup_path.into_vec();
+ cgroup_path.remove(0);
+ if cgroup_path.is_empty() {
+ return Err("cgroup path cannot be /".to_owned());
+ }
+ let cgroup_path = OsString::from_vec(cgroup_path);
+ check_path(&cgroup_path)?;
+ Ok(cgroup_path.into())
+ } else if cgroup_path.is_empty() {
+ Err("cgroup path cannot be empty".to_owned())
+ } else {
+ check_path(&cgroup_path)?;
+ let mut local_cgroup = local_cgroup()?;
+ local_cgroup.push(cgroup_path);
+ Ok(local_cgroup)
+ }
+}
+
+/// Open the cgroup corresponding to the provided path.
+/// It must have already been made relative to `/sys/fs/cgroup`.
+///
+/// # Errors
+///
+/// Fails if the cgroup operation fails.
+fn open_cgroup(path: &Path, exclusive: bool) -> Result<Cgroup, String> {
+ if path.as_os_str().is_empty() {
+ Cgroup::new(exclusive)
+ } else {
+ let mut cgroup = Cgroup::new(false)?;
+ cgroup.open_sub_cgroup(path, exclusive, false)?;
+ Ok(cgroup)
+ }
+}
+
+/// Open the cgroup corresponding to the provided path's parent.
+/// It is made relative to the process's own cgroup if needed.
+///
+/// # Errors
+///
+/// Fails if the cgroup operation fails.
+fn open_relative_cgroup(arg: OsString) -> Result<(PathBuf, Cgroup), String> {
+ let path = get_cgroup(arg)?;
+ let cgroup = open_cgroup(path.parent().expect("always has a parent"), true)?;
+ Ok((path, cgroup))
+}
+
+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 main_(&prog_name, args) {
+ Ok(()) => {}
+ Err(e) => {
+ eprintln!("{prog_name:?}: {}", e);
+ std::process::exit(1);
+ }
+ }
+}
+
+fn main_(prog_name: &OsStr, mut args: std::env::ArgsOs) -> Result<(), String> {
+ match prog_name
+ .as_bytes()
+ .split(|&b| b == b'/')
+ .next_back()
+ .unwrap()
+ {
+ b"cgroup-s6-finish" => {
+ return s6_finish(&mut args);
+ }
+ b"cgroup-setup" => {}
+ b"cgroup-purge" => {
+ if args.len() != 1 {
+ return Err(format!(
+ "cgroup-purge takes one argument, got {}",
+ args.len()
+ ));
+ }
+ let cgroup_path = args.next().unwrap();
+ let (path, mut cgroup) = open_relative_cgroup(cgroup_path)?;
+ let cgroup_target = Path::new(path.file_name().unwrap());
+ return cgroup.purge(cgroup_target);
+ }
+ _ => {
+ return Err(format!(
+ "must be invoked as \"cgroup-setup\" \
+ \"cgroup-purge\", or \"cgroup-s6-finish\", \
+ got {prog_name:?}",
+ ));
+ }
+ };
+ let mut leaf = false;
+ let mut cgroup_path;
+ let mut delegate = false;
+ let mut init_subtree = false;
+ let mut child_name: Option<&'static OsStr> = None;
+ let mut wait = true;
+ loop {
+ cgroup_path = args.next();
+ let Some(ref arg_) = cgroup_path else {
+ break;
+ };
+ let arg_ = arg_.as_bytes();
+ if arg_ == b"--" {
+ cgroup_path = args.next();
+ break;
+ }
+ if !arg_.starts_with(b"-") {
+ break;
+ }
+
+ if !arg_.starts_with(b"--") {
+ return Err("takes no short options".to_owned());
+ }
+
+ match &arg_[2..] {
+ b"leaf" => leaf = true,
+ b"delegate" => delegate = true,
+ b"init-subtree" => init_subtree = true,
+ b"wait" => wait = true,
+ b"no-wait" => wait = false,
+ b"child-name" if child_name.is_none() => match args.next() {
+ Some(arg) => child_name = Some(arg.leak()),
+ None => return Err("--child-name: missing argument".to_owned()),
+ },
+ b"child-name" => return Err("--child-name: cannot be used twice".to_owned()),
+ arg => match str::from_utf8(arg) {
+ Ok(e) => return Err(format!("unknown long option {e:?}")),
+ Err(_) => return Err("long option isn't UTF-8".to_owned()),
+ },
+ }
+ }
+
+ let default_child_name = OsStr::from_bytes(b"$inner.service");
+
+ let child_name = Path::new(child_name.unwrap_or(default_child_name));
+
+ let Some(mut cgroup_path) = cgroup_path else {
+ return Err("have no positional arguments, expected at least 1".to_owned());
+ };
+
+ // Allow --init-subtree .
+ if cgroup_path.as_bytes() == b"." && init_subtree && !leaf {
+ cgroup_path = child_name.to_owned().into();
+ leaf = true;
+ }
+
+ let (path, mut cgroup) = open_relative_cgroup(cgroup_path)?;
+ let cgroup_target = Path::new(path.file_name().unwrap());
+ cgroup
+ .make_child(cgroup_target)
+ .map_err(|e| format!("Cannot make child cgroup: {e}"))?;
+ if wait {
+ cgroup
+ .wait_for_empty()
+ .map_err(|e| format!("Cannot wait for {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.
+ cgroup
+ .write_cgroup_value("cgroup.procs", &pid)
+ .map_err(|e| format!("Cannot write to {path:?}/cgroup.procs: {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.
+ cgroup.make_child(Path::new(child_name)).map_err(|e| {
+ format!(
+ "Cannot create child cgroup {}/{}: {e}",
+ path.display(),
+ child_name.display()
+ )
+ })?;
+ if args.len() != 0 {
+ cgroup
+ .write_cgroup_value("cgroup.procs", &pid)
+ .map_err(|e| {
+ format!(
+ "Cannot write to {}/{}/cgroup.procs: {e}",
+ path.display(),
+ child_name.display()
+ )
+ })?;
+ }
+ }
+ if !leaf {
+ cgroup.enable_subtree_control(2)?;
+ }
+ if init_subtree {
+ cgroup.enable_subtree_control(1)?;
+ }
+ if delegate {
+ cgroup
+ .enable_delegation(1)
+ .map_err(|e| format!("Cannot enable cgroup delegation in {path:?}: {e}"))?;
+ }
+ 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 s6_finish(args: &mut std::env::ArgsOs) -> Result<(), String> {
+ if args.len() < 3 {
+ return Err(format!(
+ "s6 finish scripts take at least 3 arguments, got {}",
+ args.len()
+ ));
+ }
+ let status = parse_digit_string(&args.next().unwrap(), "exit status")?;
+ let signal = args.next().unwrap();
+ let signal = if status == 256 {
+ Some(parse_digit_string(&signal, "signal number")?)
+ } else {
+ None
+ };
+ let service = args.next().unwrap();
+
+ let (path, mut cgroup) = open_relative_cgroup(service)?;
+ let cgroup_target = Path::new(path.file_name().unwrap());
+ let exit_125 = if let Some(signal) = signal {
+ match signal as libc::c_int {
+ libc::SIGBUS
+ | libc::SIGFPE
+ | libc::SIGABRT
+ | libc::SIGTRAP
+ | libc::SIGSEGV
+ | libc::SIGILL => {
+ // Process *crashed*, indicating a *possible exploit attempt*.
+ // s6 should *not* restart it. This is distinct from a Rust panic,
+ // which is much less likely to indicate memory corruption.
+ true
+ }
+ _ => false,
+ }
+ } else {
+ false
+ };
+ if exit_125 {
+ // Ignore panics. Exit status is more important.
+ // We already had a core dump.
+ let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+ match cgroup.purge(cgroup_target) {
+ Ok(()) => {}
+ Err(e) => {
+ eprintln!("cgroup-s6-finish: Failed to purge cgroup: {e}")
+ }
+ };
+ }));
+ std::process::exit(125)
+ } else {
+ cgroup.purge(cgroup_target)
+ }
+}
+
+fn parse_digit_string(digits: &OsStr, msg: &str) -> Result<u16, String> {
+ let checked = match str::from_utf8(digits.as_bytes()) {
+ Ok(s) => s,
+ Err(e) => return Err(format!("{msg} is not UTF-8: {e}")),
+ };
+ let r = checked
+ .parse::<u16>()
+ .map_err(|e| format!("{msg} {digits:?} is a bad 16-bit number: {e}"))?;
+ match checked.as_bytes() {
+ b"0" | [b'1'..=b'9', ..] => Ok(r),
+ [b'0', ..] => Err(format!("{msg} {} has a leading 0", digits.display())),
+ _ => Err(format!("{msg} {} starts with +", digits.display())),
+ }
+}
+
+fn local_cgroup() -> Result<PathBuf, String> {
+ let mut local_cgroup: Vec<u8> = std::fs::read("/proc/thread-self/cgroup")
+ .map_err(|e| format!("cannot read /proc/thread-self/cgroup: {e}"))?;
+ let local_cgroup_len = local_cgroup.len();
+ if local_cgroup_len < 5
+ || local_cgroup[..4] != *b"0::/"
+ || local_cgroup[local_cgroup_len - 1] != b'\n'
+ || local_cgroup[4..local_cgroup_len - 1].contains(&b'\n')
+ {
+ // It's possible to get here if the cgroup path contains a newline,
+ // but that never happens in Spectrum.
+ return Err(format!(
+ "Invalid contents {local_cgroup:?} of /proc/thread-self/cgroup - \
+ do you have cgroups v1 mounted instead of cgroups v2?"
+ ));
+ }
+
+ local_cgroup.copy_within(4..local_cgroup_len - 1, 0);
+ local_cgroup.truncate(local_cgroup_len - 5);
+ let local_cgroup = OsString::from_vec(local_cgroup);
+ check_path(&local_cgroup).unwrap();
+ Ok(PathBuf::from(local_cgroup))
+}
--
2.55.0
next prev parent reply other threads:[~2026-07-22 2:16 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 ` Demi Marie Obenour [this message]
2026-07-22 16:01 ` [PATCH v4 02/20] tools: Add control group manager 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 ` [PATCH v6 02/19] tools: Add control group manager Demi Marie Obenour
2026-08-06 6:58 ` 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=20260721-cgroups-v4-2-46b2e5fff7b6@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).