diff --git a/src/main.rs b/src/main.rs index a6d293f..cb6710a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use cosmic_notifications_util::{DAEMON_NOTIFICATIONS_FD, PANEL_NOTIFICATIONS_FD} use futures_util::StreamExt; use launch_pad::{process::Process, ProcessManager}; use service::SessionRequest; +use systemd::{is_systemd_used, spawn_scope}; use tokio::{ net::UnixStream, sync::{ @@ -356,7 +357,7 @@ async fn start_component( } let (extra_fd_env, _): (Vec<_>, Vec<_>) = extra_fd_env.into_iter().unzip(); fds.push(fd); - process_manager + let key = process_manager .start( Process::new() .with_executable(cmd) @@ -414,4 +415,13 @@ async fn start_component( ) .await .unwrap_or_else(|_| panic!("failed to start {}", cmd)); + if *is_systemd_used() { + //currently pid is optional hence the double unwrap + let pids = process_manager.get_pid(key).await.unwrap().unwrap(); + //spawn_scope takes a vec of pids in case we want to spawn a scope for multiple processes + spawn_scope(&format!("{cmd}.scope"), vec![pids]) + .await + .unwrap(); + } + process_manager.get_pid(key).await.unwrap(); } diff --git a/src/systemd.rs b/src/systemd.rs index 31be26b..080e1c2 100644 --- a/src/systemd.rs +++ b/src/systemd.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only use std::process::{Command, Stdio}; +use std::sync::OnceLock; + +use zbus::zvariant::{Array, OwnedValue}; +use zbus::{proxy, zvariant::Value, Connection}; pub async fn set_systemd_environment(key: &str, value: &str) { run_optional_command( @@ -23,6 +27,53 @@ pub fn stop_systemd_target() { ) } +///Determine if systemd is used as the init system. This should work on all linux distributions. +pub fn is_systemd_used() -> &'static bool { + static IS_SYSTEMD_USED: OnceLock = OnceLock::new(); + IS_SYSTEMD_USED.get_or_init( + || match Command::new("readlink").args(&["/sbin/init"]).output() { + Ok(output) => { + let init = String::from_utf8_lossy(&output.stdout); + init.trim().ends_with("/lib/systemd/systemd") + } + Err(error) => { + warn!("unable to check if systemd is used: {}", error); + false + } + }, + ) +} + +#[proxy( + name = "org.freedesktop.systemd1.Manager", + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +trait SystemdManager<'a> { + fn start_transient_unit<'a>( + &self, + name: &str, + mode: &str, + properties: Vec<(String, Value<'a>)>, + //This is based on the systemd-zbus implementation, however according to the spec this should be empty + //see: https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html#:~:text=aux%20is%20currently%20unused + aux: Vec<(String, Vec<(String, Value<'a>)>)>, + ) -> zbus::Result<()>; +} + +///Spawn a systemd scope unit with the given name and PIDs. +pub async fn spawn_scope(scope_name: &str, pids: Vec) -> zbus::Result<()> { + let connection = Connection::session().await?; + let systemd_manager = SystemdManagerProxy::new(&connection).await?; + + let properties = vec![(String::from("PIDs"), Value::Array(Array::from(pids)))]; + systemd_manager + .start_transient_unit(scope_name, "fail", properties, Vec::new()) + .await?; + + Ok(()) +} + /// run a command, but log errors instead of returning them or panicking fn run_optional_command(cmd: &str, args: &[&str]) { match Command::new(cmd).args(args).stdin(Stdio::null()).status() {