From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 641EBABFC; Sat, 20 Jun 2026 14:31:49 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id 2FE67ABE0; Sat, 20 Jun 2026 14:31:46 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.1 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_PASS,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=4.0.1 Received: from mail-yw1-x1134.google.com (mail-yw1-x1134.google.com [IPv6:2607:f8b0:4864:20::1134]) by atuin.qyliss.net (Postfix) with ESMTPS id ED7A0ABDE for ; Sat, 20 Jun 2026 14:31:43 +0000 (UTC) Received: by mail-yw1-x1134.google.com with SMTP id 00721157ae682-7f5eb760240so38832917b3.0 for ; Sat, 20 Jun 2026 07:31:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781965901; x=1782570701; darn=spectrum-os.org; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:from:to:cc:subject:date:message-id:reply-to; bh=vIIEK4HZ7xquCVPofQKYy9l9TDlpbLAm7onUVTl+RuY=; b=XzmrUh5ZcKl3vEZpMDTuQOYVAlquR9i48Lrj7SuBbkLyBlLm01lJqM5iaI5Wqhrno7 9K+ZBk3OqU/w4GV92TI1fKjFnS6VwWrB8I0uOpDNV3cqjWCnVXR3Lr5CpU8TbZP4qOxU fuvtwkMAqgou/vMmW7eES2FxyI6jEBb6iXV1zfDgUriUSZipENe9CT0z7cZvLB9KuLrI fRS9bLQ0i2IJeCNgaMnbDnxYxdUJYUw81amiqNEyfsU6LOotWf0qWnq9wer0GfqqZqdS X39a6OfD9UOVFZi43VyXOmTKoDThMweIk2huRrw1UDcAjMq3zMLZXz9TUg4M1Jb436JC bGXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781965901; x=1782570701; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=vIIEK4HZ7xquCVPofQKYy9l9TDlpbLAm7onUVTl+RuY=; b=WmO1lmGyyxt4fDtY9cFivA8xfAcYY4c/I2o8TX+9lpUztJ9yOIdwX/0fcwVQFj+7Uk LteWq3WCGpy4O1ql60v2mxndUBi6VfJhefi5zfWovZ2PKl1LkQ0P/kr6UqWvWyefxyOV fkIA5nLwLSqnaRrTxxmPkLlKWN3/OWX+ZbAJuwA+bC8AtUvlT5XT3W99v52JhLcoi+Qj YaRCnn7o5DO0QRf//2YvGQQNAbpBCB7bPaerVJwRwzOB6ListGieiWFD8BoU4agjWZVQ fXq8wOfAMbksoFCcBsQFxT8CNixb9Hsx13Tea+HpKEi1e1SNFUMw3rtLb0Tlo6NFLrSW 5s7A== X-Gm-Message-State: AOJu0YxqccSqbvTIY1oSYzEe1YmDaX8z3aBtrFyFi1w2RZ5jsRir53F4 tAAOC3HtoBJltOYURFL7ai2yWfs5LJrsTbgs1aaC2GC2jXUxCA1Rtul1FiN4cw== X-Gm-Gg: AfdE7clYcNaXLoHPXlkxFRT77tf739tj9gzppMOyK5Afxi6nCRU5TvaQHncwZR68Jjp BHRxeq7xaj6T2++HSEMEVTo48M2zOdaNYR6qxLU8oNBm0ysq3K2rieffMbKrpnhpng1923XwQe9 jpADLml/COHeD2S8S8qjahsJ+5ubzCa5nOav0HA/vojSbqr4KXfUS2pJRrkl/WFfG5+7uu6hdgu 6NXtLOfs76JKMTVUNxy8WJJAx8Z8bonsNiO1JMTDI1sRC5Jf7NVG3VscQ4zDZ11Wz5GzqlPfmWR AGjTH/yhsjyFneLDUv7BBFmqGB7jmxeLEFN3MKK6tDPL7ddwdbvRwlueEMK598R3LC/PeCKvTDr TGe7aQOcp8p332hLHqB+JNHOhcJrpeOstZ8XG59EWyUSdOXkWr8dAjgz9xu0HXJuVwNhUjEgdV9 640TQ4tPIPkVhAP+U+4XzrU83q7ud5mViEgloS9YqD4vCZHxwYj6FtAcRxxk+XoJ6vZTgCIAY0R ZAolwHzH8C4K12WT7JXJYNXmoPvV0PJHV96 X-Received: by 2002:a05:690c:45c4:b0:7f8:7e6f:f543 with SMTP id 00721157ae682-80134987371mr86913267b3.49.1781965900506; Sat, 20 Jun 2026 07:31:40 -0700 (PDT) Received: from localhost.localdomain (h69-131-147-66.cncrtn.broadband.dynamic.tds.net. [69.131.147.66]) by smtp.gmail.com with UTF8SMTPSA id 00721157ae682-8025c96fdb3sm10974417b3.1.2026.06.20.07.31.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 20 Jun 2026 07:31:36 -0700 (PDT) From: Demi Marie Obenour Date: Sat, 20 Jun 2026 10:23:09 -0400 Subject: [PATCH] Set up control groups for most services MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Message-Id: <20260620-cgroups-v1-1-0e5abf35101b@gmail.com> X-B4-Tracking: v=1; b=H4sIAAAAAAAC/6tWKk4tykwtVrJSqFYqSi3LLM7MzwNyDHUUlJIzE vPSU3UzU4B8JSMDIzMDUyML3eT0ovzSgmLdFDMDy1Qjc4NUMxNLJaDqgqLUtMwKsEnRsRB+cWl SVmpyCUi7Um0tAGczr/BrAAAA X-Change-ID: 20260528-cgroups-d609e270e649 To: Spectrum OS Development X-Mailer: b4 0.15.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1781965388; l=51051; i=demiobenour@gmail.com; s=20250729; h=from:subject:message-id; bh=24Y5hltciQEnjdHSB4qfYB4Pc/jhLxAkvziz8BxU+Z8=; b=DztNBeOfqi11xv7nK5hYCmMboRozVrP6LHuNxXHhjY6/6UqW0zlA4m5BieN4ZPQGiAXGmLvWX w8NjvM9VRhlASVq/lVSEaszBVcM4e9HTJdQsI5Ik2KRXwdktIxWcZbU X-Developer-Key: i=demiobenour@gmail.com; a=ed25519; pk=X57Q4/YQDj9t4SBeKaDwvXYKB6quZJVx/DE2Ly2out0= Message-ID-Hash: GYU3TRH5GCKF33LBTJ2MK4OCCRWGT54K X-Message-ID-Hash: GYU3TRH5GCKF33LBTJ2MK4OCCRWGT54K X-MailFrom: demiobenour@gmail.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Alyssa Ross , Demi Marie Obenour X-Mailman-Version: 3.3.10 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 --- .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 +# 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 +# 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 +# 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 +# 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 +# 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 +# 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 + +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 +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 + +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 +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 +# 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 + +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 +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 + +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 +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 + +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 # SPDX-FileCopyrightText: 2025 Alyssa Ross +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 + +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 +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 + +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 # SPDX-FileCopyrightText: 2025 Demi Marie Obenour +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 + +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 +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 + +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 +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 + +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 +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 + +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 -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 + +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 +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 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 +# SPDX-FileCopyrightText: 2026 Demi Marie Obenour + +[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 +# SPDX-FileCopyrightText: 2025 Yureka Lilian +# 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 + +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 { + 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 { + 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 { + 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 + +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(::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(::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)