From 4a68c762e18b4c3c6693a1c58b69061012459d0c Mon Sep 17 00:00:00 2001 From: Xuewei Niu Date: Thu, 3 Jul 2025 20:02:03 +0800 Subject: [PATCH] systemd: Add support for managing cgroups through systemd `SystemdCgroup` takes a `parent`, which is the name of a slice, and a `unit`, which is the name of a slice or a scope unit, and provides methods to start, kill the unit, as well as set properties for the unit. The mods, `cpu`, `memory`, `cpuset`, and `pids`, are designed to generate properties quickly. It hides the difference between cgroups v1 and v2, and does simple checks for the systemd version and arguments. Signed-off-by: Xuewei Niu --- Cargo.toml | 2 + src/lib.rs | 1 + src/systemd/consts.rs | 68 ++ src/systemd/cpu.rs | 35 + src/systemd/cpuset.rs | 106 +++ src/systemd/dbus/README.md | 17 + src/systemd/dbus/client.rs | 192 ++++ src/systemd/dbus/error.rs | 18 + src/systemd/dbus/mod.rs | 18 + src/systemd/dbus/proxy.rs | 16 + src/systemd/dbus/systemd_manager_proxy.rs | 1011 +++++++++++++++++++++ src/systemd/error.rs | 18 + src/systemd/memory.rs | 32 + src/systemd/mod.rs | 25 + src/systemd/pids.rs | 11 + src/systemd/props.rs | 170 ++++ src/systemd/utils.rs | 64 ++ 17 files changed, 1804 insertions(+) create mode 100644 src/systemd/consts.rs create mode 100644 src/systemd/cpu.rs create mode 100644 src/systemd/cpuset.rs create mode 100644 src/systemd/dbus/README.md create mode 100644 src/systemd/dbus/client.rs create mode 100644 src/systemd/dbus/error.rs create mode 100644 src/systemd/dbus/mod.rs create mode 100644 src/systemd/dbus/proxy.rs create mode 100644 src/systemd/dbus/systemd_manager_proxy.rs create mode 100644 src/systemd/error.rs create mode 100644 src/systemd/memory.rs create mode 100644 src/systemd/mod.rs create mode 100644 src/systemd/pids.rs create mode 100644 src/systemd/props.rs create mode 100644 src/systemd/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 4448783..f25b3bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ libc = "0.2" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "1" oci-spec = { version = "0.8.1", optional = true } +zbus = "5.8" +bit-vec = "0.6" [dev-dependencies] libc = "0.2.76" diff --git a/src/lib.rs b/src/lib.rs index 4bb4a3b..1f163a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod manager; pub use manager::{FsManager, Manager}; pub mod stats; pub use stats::CgroupStats; +pub mod systemd; /// The maximum value for CPU shares in cgroups v1 pub const CPU_SHARES_V1_MAX: u64 = 262144; diff --git a/src/systemd/consts.rs b/src/systemd/consts.rs new file mode 100644 index 0000000..9ce477a --- /dev/null +++ b/src/systemd/consts.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +/// Who enum: all +pub const WHO_ENUM_ALL: &str = "all"; + +/// Unit mode: replace +pub const UNIT_MODE_REPLACE: &str = "replace"; + +/// No such unit error +pub const NO_SUCH_UNIT: &str = "org.freedesktop.systemd1.NoSuchUnit"; + +/// Default description for transient units. +pub const DEFAULT_DESCRIPTION: &str = "cgroups-rs transient unit"; + +/// Turn on CPU usage accounting for this unit. +pub const CPU_ACCOUNTING: &str = "CPUAccounting"; +/// This setting controls the memory controller in the unified hierarchy. +/// Added in version 208. +pub const MEMORY_ACCOUNTING: &str = "MemoryAccounting"; +/// This setting controls the pids controller in the unified hierarchy. +pub const TASKS_ACCOUNTING: &str = "TasksAccounting"; +/// This setting controls the io controller in the unified hierarchy. +/// Added in version 230. +pub const IO_ACCOUNTING: &str = "IOAccounting"; +/// This setting controls the block IO controller in the legacy hierarchy. +/// Deprecated in version 252. +pub const BLOCK_IO_ACCOUNTING: &str = "BlockIOAccounting"; +/// Description of the unit. +pub const DESCRIPTION: &str = "Description"; +/// PIDs +pub const PIDS: &str = "PIDs"; +/// Default dependencies for this unit. +pub const DEFAULT_DEPENDENCIES: &str = "DefaultDependencies"; +/// Wants, expressing a weak dependency on other units. +pub const WANTS: &str = "Wants"; +/// Slice, used to assign a unit to a specific slice. +pub const SLICE: &str = "Slice"; +/// Turns on delegation of further resource control partitioning to +/// processes of the unit. +pub const DELEGATE: &str = "Delegate"; +/// Timeout for stopping the unit in microseconds. +pub const TIMEOUT_STOP_USEC: &str = "TimeoutStopUSec"; + +/// CPU shares in the legacy hierarchy. +pub const CPU_SHARES: &str = "CPUShares"; +/// CPU shares in the unified hierarchy. +pub const CPU_WEIGHT: &str = "CPUWeight"; +/// CPU quota period us. +pub const CPU_QUOTA_PERIOD_US: &str = "CPUQuotaPeriodUSec"; +/// CPU quota us +pub const CPU_QUOTA_PER_SEC_US: &str = "CPUQuotaPerSecUSec"; +/// Allowed CPUs +pub const ALLOWED_CPUS: &str = "AllowedCPUs"; +/// Allowed memory nodes +pub const ALLOWED_MEMORY_NODES: &str = "AllowedMemoryNodes"; +/// Memory limit in the legacy hierarchy. +pub const MEMORY_LIMIT: &str = "MemoryLimit"; +/// Memory limit in the unified hierarchy. +pub const MEMORY_MAX: &str = "MemoryMax"; +/// Memory low +pub const MEMORY_LOW: &str = "MemoryLow"; +/// Memory swap max +pub const MEMORY_SWAP_MAX: &str = "MemorySwapMax"; +/// Tasks max +pub const TASKS_MAX: &str = "TasksMax"; diff --git a/src/systemd/cpu.rs b/src/systemd/cpu.rs new file mode 100644 index 0000000..2256866 --- /dev/null +++ b/src/systemd/cpu.rs @@ -0,0 +1,35 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use crate::systemd::error::{Error, Result}; +use crate::systemd::{ + CPU_QUOTA_PERIOD_US, CPU_QUOTA_PER_SEC_US, CPU_SHARES, CPU_SYSTEMD_VERSION, CPU_WEIGHT, +}; + +/// Returns the property for CPU shares. +/// +/// Please note that if the shares is obtained from OCI runtime spec, it +/// MUST be converted, see [1] and `convert_shares_to_v2()`. +/// +/// 1: https://github.com/containers/crun/blob/main/crun.1.md#cgroup-v2 +pub fn shares(shares: u64, v2: bool) -> Result<(&'static str, u64)> { + let id = if v2 { CPU_WEIGHT } else { CPU_SHARES }; + + Ok((id, shares)) +} + +/// Returns the property for CPU period. +pub fn period(period: u64, systemd_version: usize) -> Result<(&'static str, u64)> { + if systemd_version < CPU_SYSTEMD_VERSION { + return Err(Error::ObsoleteSystemd); + } + + Ok((CPU_QUOTA_PERIOD_US, period)) +} + +/// Return the property for CPU quota. +pub fn quota(quota: u64) -> Result<(&'static str, u64)> { + Ok((CPU_QUOTA_PER_SEC_US, quota)) +} diff --git a/src/systemd/cpuset.rs b/src/systemd/cpuset.rs new file mode 100644 index 0000000..b11cbcc --- /dev/null +++ b/src/systemd/cpuset.rs @@ -0,0 +1,106 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use bit_vec::BitVec; + +use crate::systemd::error::{Error, Result}; +use crate::systemd::{ALLOWED_CPUS, ALLOWED_MEMORY_NODES, CPUSET_SYSTEMD_VERSION}; + +const BYTE_IN_BITS: usize = 8; + +/// Returns the property for cpuset CPUs. +pub fn cpus(cpus: &str, systemd_version: usize) -> Result<(&'static str, Vec)> { + if systemd_version < CPUSET_SYSTEMD_VERSION { + return Err(Error::ObsoleteSystemd); + } + + let mask = convert_list_to_mask(cpus)?; + + Ok((ALLOWED_CPUS, mask)) +} + +/// Returns the property for cpuset memory nodes. +pub fn mems(mems: &str, systemd_version: usize) -> Result<(&'static str, Vec)> { + if systemd_version < CPUSET_SYSTEMD_VERSION { + return Err(Error::ObsoleteSystemd); + } + + let mask = convert_list_to_mask(mems)?; + + Ok((ALLOWED_MEMORY_NODES, mask)) +} + +/// Convert cpuset cpus/mems from the string in comma-separated list format +/// to bitmask restored in `Vec`, see [1]. +/// +/// 1: https://man7.org/linux/man-pages/man7/cpuset.7.html +/// +/// # Arguments +/// +/// * `list` - A string slice that holds the list of CPUs in the format +/// "0-3,5,7". +fn convert_list_to_mask(list: &str) -> Result> { + let mut bit_vec = BitVec::from_elem(8, false); + + let local_idx = + |index: usize| -> usize { index / BYTE_IN_BITS * BYTE_IN_BITS + 7 - index % BYTE_IN_BITS }; + + for part1 in list.split(',') { + let range: Vec<&str> = part1.split('-').collect(); + match range.len() { + // x- + 1 => { + let left: usize = range[0].parse().map_err(|_| Error::InvalidArgument)?; + + while left >= bit_vec.len() { + bit_vec.grow(BYTE_IN_BITS, false); + } + bit_vec.set(local_idx(left), true); + } + // x-y + 2 => { + let left: usize = range[0].parse().map_err(|_| Error::InvalidArgument)?; + let right: usize = range[1].parse().map_err(|_| Error::InvalidArgument)?; + + while right >= bit_vec.len() { + bit_vec.grow(BYTE_IN_BITS, false); + } + + for index in left..=right { + bit_vec.set(local_idx(index), true); + } + } + _ => { + return Err(Error::InvalidArgument); + } + } + } + + let mut mask = bit_vec.to_bytes(); + mask.reverse(); + + Ok(mask) +} + +#[cfg(test)] +mod tests { + use crate::systemd::cpuset::convert_list_to_mask; + + #[test] + fn test_convert_list_to_mask() { + let mask = convert_list_to_mask("2-4").unwrap(); + assert_eq!(vec![0b00011100_u8], mask); + + let mask = convert_list_to_mask("1,7").unwrap(); + assert_eq!(vec![0b10000010_u8], mask); + + let mask = convert_list_to_mask("0-4,9").unwrap(); + assert_eq!(vec![0b00000010_u8, 0b00011111_u8], mask); + + assert!(convert_list_to_mask("1-3-4").is_err()); + + assert!(convert_list_to_mask("1-3,,").is_err()); + } +} diff --git a/src/systemd/dbus/README.md b/src/systemd/dbus/README.md new file mode 100644 index 0000000..8461461 --- /dev/null +++ b/src/systemd/dbus/README.md @@ -0,0 +1,17 @@ +# Systemd Dbus + +How to generate `xxx_proxy.rs` files + +```shell +# install zbus-xmlgen if not +$ cargo install zbus-xmlgen +# generate interface in XML format +$ busctl introspect --xml-interface \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager > /tmp/systemd1-manager.xml +# generate Rust code from XML +$ zbus-xmlgen file /tmp/systemd1-manager.xml \ + --output src/systemd/dbus/systemd_manager_proxy.rs +$ rm -rf /tmp/systemd1-manager.xml +``` diff --git a/src/systemd/dbus/client.rs b/src/systemd/dbus/client.rs new file mode 100644 index 0000000..27305f0 --- /dev/null +++ b/src/systemd/dbus/client.rs @@ -0,0 +1,192 @@ +// Copyright 2021-2023 Kata Contributors +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use zbus::zvariant::Value; +use zbus::{Error as ZbusError, Result as ZbusResult}; + +use crate::systemd::dbus::error::{Error, Result}; +use crate::systemd::dbus::proxy::systemd_manager_proxy; +use crate::systemd::{Property, NO_SUCH_UNIT, PIDS, UNIT_MODE_REPLACE}; +use crate::CgroupPid; + +pub struct SystemdClient<'a> { + /// The name of the systemd unit (slice or scope) + unit: String, + props: Vec>, +} + +impl<'a> SystemdClient<'a> { + pub fn new(unit: &str, props: Vec>) -> Result { + Ok(Self { + unit: unit.to_string(), + props, + }) + } +} + +impl SystemdClient<'_> { + /// Set the pid to the PIDs property of the unit. + /// + /// Append a process ID to the PIDs property of the unit. If not + /// exists, one property will be created. + pub fn set_pid_prop(&mut self, pid: CgroupPid) -> Result<()> { + if self.exists() { + return Ok(()); + } + + for prop in self.props.iter_mut() { + if prop.0 == PIDS { + // If PIDS is already set, we append the new pid to the existing list. + if let Value::Array(arr) = &mut prop.1 { + arr.append(pid.pid.into()) + .map_err(|_| Error::InvalidProperties)?; + return Ok(()); + } + // Invalid type of PIDs + return Err(Error::InvalidProperties); + } + } + // If PIDS is not set, we create a new property. + self.props + .push((PIDS, Value::Array(vec![pid.pid as u32].into()))); + Ok(()) + } + + /// Start a slice or a scope unit controlled and supervised by systemd. + /// + /// For more information, see: + /// https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html + /// https://www.freedesktop.org/software/systemd/man/latest/systemd.slice.html + /// https://www.freedesktop.org/software/systemd/man/latest/systemd.scope.html + pub fn start(&self) -> Result<()> { + // PIDs property must be present + if !self.props.iter().any(|(k, _)| k == &PIDS) { + return Err(Error::InvalidProperties); + } + + let sys_proxy = systemd_manager_proxy()?; + + let props_borrowed: Vec<(&str, &zbus::zvariant::Value)> = + self.props.iter().map(|(k, v)| (*k, v)).collect(); + let props_borrowed: Vec<&(&str, &Value)> = props_borrowed.iter().collect(); + + sys_proxy.start_transient_unit(&self.unit, UNIT_MODE_REPLACE, &props_borrowed, &[])?; + + Ok(()) + } + + /// Stop the current transient unit, the processes will be killed on + /// unit stop, see [1]. + /// + /// 1. https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillMode= + pub fn stop(&self) -> Result<()> { + let sys_proxy = systemd_manager_proxy()?; + + let ret = sys_proxy.stop_unit(&self.unit, UNIT_MODE_REPLACE); + ignore_no_such_unit(ret)?; + + // If we stop the unit and it still exists, it may be in a failed + // state, so we will try to reset it. + if self.exists() { + let ret = sys_proxy.reset_failed_unit(&self.unit); + ignore_no_such_unit(ret)?; + } + + Ok(()) + } + + /// Set properties for the unit through dbus `SetUnitProperties`. + pub fn set_properties(&mut self, properties: &[Property<'static>]) -> Result<()> { + for prop in properties { + let new = prop.1.try_clone().map_err(|_| Error::InvalidProperties)?; + // Try to update the value first, if fails, append it. + if let Some(existing) = self.props.iter_mut().find(|p| p.0 == prop.0) { + existing.1 = new; + } else { + self.props.push((prop.0, new)); + } + } + + // The unit must exist before setting properties. + if !self.exists() { + return Ok(()); + } + + let sys_proxy = systemd_manager_proxy()?; + + let props_borrowed: Vec<(&str, &Value)> = properties.iter().map(|(k, v)| (*k, v)).collect(); + let props_borrowed: Vec<&(&str, &Value)> = props_borrowed.iter().collect(); + + sys_proxy.set_unit_properties(&self.unit, true, &props_borrowed)?; + + Ok(()) + } + + /// Freeze the unit through dbus `FreezeUnit`. + pub fn freeze(&self) -> Result<()> { + let sys_proxy = systemd_manager_proxy()?; + + sys_proxy.freeze_unit(&self.unit)?; + + Ok(()) + } + + /// Thaw the frozen unit through dbus `ThawUnit`. + pub fn thaw(&self) -> Result<()> { + let sys_proxy = systemd_manager_proxy()?; + + sys_proxy.thaw_unit(&self.unit)?; + + Ok(()) + } + + /// Get the systemd version. + pub fn systemd_version(&self) -> Result { + let sys_proxy = systemd_manager_proxy()?; + + // Parse 249 from "249.11-0ubuntu3.16" + let version = sys_proxy.version()?; + let version = version + .split('.') + .next() + .and_then(|v| v.parse::().ok()) + .ok_or(Error::CorruptedSystemdVersion(version))?; + + Ok(version) + } + + /// Check if the unit exists. + pub fn exists(&self) -> bool { + let sys_proxy = match systemd_manager_proxy() { + Ok(proxy) => proxy, + _ => return false, + }; + + sys_proxy + .get_unit(&self.unit) + .map(|_| true) + .unwrap_or_default() + } + + /// Add a process (tgid) to the unit through dbus + /// `AttachProcessesToUnit`. + pub fn add_process(&self, pid: CgroupPid, subcgroup: &str) -> Result<()> { + let sys_proxy = systemd_manager_proxy()?; + + sys_proxy.attach_processes_to_unit(&self.unit, subcgroup, &[pid.pid as u32])?; + + Ok(()) + } +} + +fn ignore_no_such_unit(result: ZbusResult) -> ZbusResult { + if let Err(ZbusError::MethodError(err_name, _, _)) = &result { + if err_name.as_str() == NO_SUCH_UNIT { + return Ok(true); + } + } + result.map(|_| false) +} diff --git a/src/systemd/dbus/error.rs b/src/systemd/dbus/error.rs new file mode 100644 index 0000000..d8cae94 --- /dev/null +++ b/src/systemd/dbus/error.rs @@ -0,0 +1,18 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invalid properties")] + InvalidProperties, + + #[error("dbus error: {0}")] + Dbus(#[from] zbus::Error), + + #[error("corrupted systemd version: {0}")] + CorruptedSystemdVersion(String), +} diff --git a/src/systemd/dbus/mod.rs b/src/systemd/dbus/mod.rs new file mode 100644 index 0000000..8d39ef8 --- /dev/null +++ b/src/systemd/dbus/mod.rs @@ -0,0 +1,18 @@ +// Copyright (c) 2018 Levente Kurusa +// Copyright (c) 2020-2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +//! Systemd D-Bus interface for managing cgroups and units. +//! +//! References: +//! https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html +//! https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html +//! https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html + +mod client; +pub mod error; +mod systemd_manager_proxy; +pub use client::SystemdClient; +mod proxy; diff --git a/src/systemd/dbus/proxy.rs b/src/systemd/dbus/proxy.rs new file mode 100644 index 0000000..9ed2289 --- /dev/null +++ b/src/systemd/dbus/proxy.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use zbus::blocking::Connection; +use zbus::Result; + +use crate::systemd::dbus::systemd_manager_proxy::ManagerProxyBlocking as SystemManager; + +pub(crate) fn systemd_manager_proxy<'a>() -> Result> { + let connection = Connection::system()?; + let proxy = SystemManager::new(&connection)?; + + Ok(proxy) +} diff --git a/src/systemd/dbus/systemd_manager_proxy.rs b/src/systemd/dbus/systemd_manager_proxy.rs new file mode 100644 index 0000000..0f5dc2d --- /dev/null +++ b/src/systemd/dbus/systemd_manager_proxy.rs @@ -0,0 +1,1011 @@ +// Copyright 2021-2023 Kata Contributors +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +//! # D-Bus interface proxy for: `org.freedesktop.systemd1.Manager` +//! +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data. +//! Source: `systemd1-manager.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, + +#![allow(clippy::type_complexity)] + +use zbus::proxy; + +#[proxy( + interface = "org.freedesktop.systemd1.Manager", + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +pub trait Manager { + /// AbandonScope method + fn abandon_scope(&self, name: &str) -> zbus::Result<()>; + + /// AddDependencyUnitFiles method + fn add_dependency_unit_files( + &self, + files: &[&str], + target: &str, + type_: &str, + runtime: bool, + force: bool, + ) -> zbus::Result>; + + /// AttachProcessesToUnit method + fn attach_processes_to_unit( + &self, + unit_name: &str, + subcgroup: &str, + pids: &[u32], + ) -> zbus::Result<()>; + + /// BindMountUnit method + fn bind_mount_unit( + &self, + name: &str, + source: &str, + destination: &str, + read_only: bool, + mkdir: bool, + ) -> zbus::Result<()>; + + /// CancelJob method + fn cancel_job(&self, id: u32) -> zbus::Result<()>; + + /// CleanUnit method + fn clean_unit(&self, name: &str, mask: &[&str]) -> zbus::Result<()>; + + /// ClearJobs method + fn clear_jobs(&self) -> zbus::Result<()>; + + /// DisableUnitFiles method + fn disable_unit_files( + &self, + files: &[&str], + runtime: bool, + ) -> zbus::Result>; + + /// DisableUnitFilesWithFlags method + fn disable_unit_files_with_flags( + &self, + files: &[&str], + flags: u64, + ) -> zbus::Result>; + + /// Dump method + fn dump(&self) -> zbus::Result; + + /// DumpByFileDescriptor method + fn dump_by_file_descriptor(&self) -> zbus::Result; + + /// EnableUnitFiles method + fn enable_unit_files( + &self, + files: &[&str], + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + /// EnableUnitFilesWithFlags method + fn enable_unit_files_with_flags( + &self, + files: &[&str], + flags: u64, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + /// EnqueueMarkedJobs method + fn enqueue_marked_jobs(&self) -> zbus::Result>; + + /// EnqueueUnitJob method + #[allow(clippy::too_many_arguments)] + fn enqueue_unit_job( + &self, + name: &str, + job_type: &str, + job_mode: &str, + ) -> zbus::Result<( + u32, + zbus::zvariant::OwnedObjectPath, + String, + zbus::zvariant::OwnedObjectPath, + String, + Vec<( + u32, + zbus::zvariant::OwnedObjectPath, + String, + zbus::zvariant::OwnedObjectPath, + String, + )>, + )>; + + /// Exit method + fn exit(&self) -> zbus::Result<()>; + + /// FreezeUnit method + fn freeze_unit(&self, name: &str) -> zbus::Result<()>; + + /// GetDefaultTarget method + fn get_default_target(&self) -> zbus::Result; + + /// GetDynamicUsers method + fn get_dynamic_users(&self) -> zbus::Result>; + + /// GetJob method + fn get_job(&self, id: u32) -> zbus::Result; + + /// GetJobAfter method + fn get_job_after( + &self, + id: u32, + ) -> zbus::Result< + Vec<( + u32, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// GetJobBefore method + fn get_job_before( + &self, + id: u32, + ) -> zbus::Result< + Vec<( + u32, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// GetUnit method + fn get_unit(&self, name: &str) -> zbus::Result; + + /// GetUnitByControlGroup method + fn get_unit_by_control_group( + &self, + cgroup: &str, + ) -> zbus::Result; + + /// GetUnitByInvocationID method + #[zbus(name = "GetUnitByInvocationID")] + fn get_unit_by_invocation_id( + &self, + invocation_id: &[u8], + ) -> zbus::Result; + + /// GetUnitByPID method + #[zbus(name = "GetUnitByPID")] + fn get_unit_by_pid(&self, pid: u32) -> zbus::Result; + + /// GetUnitFileLinks method + fn get_unit_file_links(&self, name: &str, runtime: bool) -> zbus::Result>; + + /// GetUnitFileState method + fn get_unit_file_state(&self, file: &str) -> zbus::Result; + + /// GetUnitProcesses method + fn get_unit_processes(&self, name: &str) -> zbus::Result>; + + /// Halt method + fn halt(&self) -> zbus::Result<()>; + + /// KExec method + #[zbus(name = "KExec")] + fn kexec(&self) -> zbus::Result<()>; + + /// KillUnit method + fn kill_unit(&self, name: &str, whom: &str, signal: i32) -> zbus::Result<()>; + + /// LinkUnitFiles method + fn link_unit_files( + &self, + files: &[&str], + runtime: bool, + force: bool, + ) -> zbus::Result>; + + /// ListJobs method + fn list_jobs( + &self, + ) -> zbus::Result< + Vec<( + u32, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// ListUnitFiles method + fn list_unit_files(&self) -> zbus::Result>; + + /// ListUnitFilesByPatterns method + fn list_unit_files_by_patterns( + &self, + states: &[&str], + patterns: &[&str], + ) -> zbus::Result>; + + /// ListUnits method + fn list_units( + &self, + ) -> zbus::Result< + Vec<( + String, + String, + String, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + u32, + String, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// ListUnitsByNames method + fn list_units_by_names( + &self, + names: &[&str], + ) -> zbus::Result< + Vec<( + String, + String, + String, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + u32, + String, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// ListUnitsByPatterns method + fn list_units_by_patterns( + &self, + states: &[&str], + patterns: &[&str], + ) -> zbus::Result< + Vec<( + String, + String, + String, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + u32, + String, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// ListUnitsFiltered method + fn list_units_filtered( + &self, + states: &[&str], + ) -> zbus::Result< + Vec<( + String, + String, + String, + String, + String, + String, + zbus::zvariant::OwnedObjectPath, + u32, + String, + zbus::zvariant::OwnedObjectPath, + )>, + >; + + /// LoadUnit method + fn load_unit(&self, name: &str) -> zbus::Result; + + /// LookupDynamicUserByName method + fn lookup_dynamic_user_by_name(&self, name: &str) -> zbus::Result; + + /// LookupDynamicUserByUID method + #[zbus(name = "LookupDynamicUserByUID")] + fn lookup_dynamic_user_by_uid(&self, uid: u32) -> zbus::Result; + + /// MaskUnitFiles method + fn mask_unit_files( + &self, + files: &[&str], + runtime: bool, + force: bool, + ) -> zbus::Result>; + + /// MountImageUnit method + fn mount_image_unit( + &self, + name: &str, + source: &str, + destination: &str, + read_only: bool, + mkdir: bool, + options: &[&(&str, &str)], + ) -> zbus::Result<()>; + + /// PowerOff method + fn power_off(&self) -> zbus::Result<()>; + + /// PresetAllUnitFiles method + fn preset_all_unit_files( + &self, + mode: &str, + runtime: bool, + force: bool, + ) -> zbus::Result>; + + /// PresetUnitFiles method + fn preset_unit_files( + &self, + files: &[&str], + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + /// PresetUnitFilesWithMode method + fn preset_unit_files_with_mode( + &self, + files: &[&str], + mode: &str, + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + /// Reboot method + fn reboot(&self) -> zbus::Result<()>; + + /// ReenableUnitFiles method + fn reenable_unit_files( + &self, + files: &[&str], + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + /// Reexecute method + fn reexecute(&self) -> zbus::Result<()>; + + /// RefUnit method + fn ref_unit(&self, name: &str) -> zbus::Result<()>; + + /// Reload method + fn reload(&self) -> zbus::Result<()>; + + /// ReloadOrRestartUnit method + fn reload_or_restart_unit( + &self, + name: &str, + mode: &str, + ) -> zbus::Result; + + /// ReloadOrTryRestartUnit method + fn reload_or_try_restart_unit( + &self, + name: &str, + mode: &str, + ) -> zbus::Result; + + /// ReloadUnit method + fn reload_unit(&self, name: &str, mode: &str) -> zbus::Result; + + /// ResetFailed method + fn reset_failed(&self) -> zbus::Result<()>; + + /// ResetFailedUnit method + fn reset_failed_unit(&self, name: &str) -> zbus::Result<()>; + + /// RestartUnit method + fn restart_unit(&self, name: &str, mode: &str) + -> zbus::Result; + + /// RevertUnitFiles method + fn revert_unit_files(&self, files: &[&str]) -> zbus::Result>; + + /// SetDefaultTarget method + fn set_default_target( + &self, + name: &str, + force: bool, + ) -> zbus::Result>; + + /// SetEnvironment method + fn set_environment(&self, assignments: &[&str]) -> zbus::Result<()>; + + /// SetExitCode method + fn set_exit_code(&self, number: u8) -> zbus::Result<()>; + + /// SetShowStatus method + fn set_show_status(&self, mode: &str) -> zbus::Result<()>; + + /// SetUnitProperties method + fn set_unit_properties( + &self, + name: &str, + runtime: bool, + properties: &[&(&str, &zbus::zvariant::Value<'_>)], + ) -> zbus::Result<()>; + + /// StartTransientUnit method + #[allow(clippy::type_complexity)] + fn start_transient_unit( + &self, + name: &str, + mode: &str, + properties: &[&(&str, &zbus::zvariant::Value<'_>)], + aux: &[&(&str, &[&(&str, &zbus::zvariant::Value<'_>)])], + ) -> zbus::Result; + + /// StartUnit method + fn start_unit(&self, name: &str, mode: &str) -> zbus::Result; + + /// StartUnitReplace method + fn start_unit_replace( + &self, + old_unit: &str, + new_unit: &str, + mode: &str, + ) -> zbus::Result; + + /// StopUnit method + fn stop_unit(&self, name: &str, mode: &str) -> zbus::Result; + + /// Subscribe method + fn subscribe(&self) -> zbus::Result<()>; + + /// SwitchRoot method + fn switch_root(&self, new_root: &str, init: &str) -> zbus::Result<()>; + + /// ThawUnit method + fn thaw_unit(&self, name: &str) -> zbus::Result<()>; + + /// TryRestartUnit method + fn try_restart_unit( + &self, + name: &str, + mode: &str, + ) -> zbus::Result; + + /// UnmaskUnitFiles method + fn unmask_unit_files( + &self, + files: &[&str], + runtime: bool, + ) -> zbus::Result>; + + /// UnrefUnit method + fn unref_unit(&self, name: &str) -> zbus::Result<()>; + + /// UnsetAndSetEnvironment method + fn unset_and_set_environment(&self, names: &[&str], assignments: &[&str]) -> zbus::Result<()>; + + /// UnsetEnvironment method + fn unset_environment(&self, names: &[&str]) -> zbus::Result<()>; + + /// Unsubscribe method + fn unsubscribe(&self) -> zbus::Result<()>; + + /// JobNew signal + #[zbus(signal)] + fn job_new(&self, id: u32, job: zbus::zvariant::ObjectPath<'_>, unit: &str) + -> zbus::Result<()>; + + /// JobRemoved signal + #[zbus(signal)] + fn job_removed( + &self, + id: u32, + job: zbus::zvariant::ObjectPath<'_>, + unit: &str, + result: &str, + ) -> zbus::Result<()>; + + /// Reloading signal + #[zbus(signal)] + fn reloading(&self, active: bool) -> zbus::Result<()>; + + /// StartupFinished signal + #[zbus(signal)] + fn startup_finished( + &self, + firmware: u64, + loader: u64, + kernel: u64, + initrd: u64, + userspace: u64, + total: u64, + ) -> zbus::Result<()>; + + /// UnitFilesChanged signal + #[zbus(signal)] + fn unit_files_changed(&self) -> zbus::Result<()>; + + /// UnitNew signal + #[zbus(signal)] + fn unit_new(&self, id: &str, unit: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// UnitRemoved signal + #[zbus(signal)] + fn unit_removed(&self, id: &str, unit: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// Architecture property + #[zbus(property)] + fn architecture(&self) -> zbus::Result; + + /// ConfirmSpawn property + #[zbus(property)] + fn confirm_spawn(&self) -> zbus::Result; + + /// ControlGroup property + #[zbus(property)] + fn control_group(&self) -> zbus::Result; + + /// CtrlAltDelBurstAction property + #[zbus(property)] + fn ctrl_alt_del_burst_action(&self) -> zbus::Result; + + /// DefaultBlockIOAccounting property + #[zbus(property, name = "DefaultBlockIOAccounting")] + fn default_block_ioaccounting(&self) -> zbus::Result; + + /// DefaultCPUAccounting property + #[zbus(property, name = "DefaultCPUAccounting")] + fn default_cpuaccounting(&self) -> zbus::Result; + + /// DefaultLimitAS property + #[zbus(property, name = "DefaultLimitAS")] + fn default_limit_as(&self) -> zbus::Result; + + /// DefaultLimitASSoft property + #[zbus(property, name = "DefaultLimitASSoft")] + fn default_limit_assoft(&self) -> zbus::Result; + + /// DefaultLimitCORE property + #[zbus(property, name = "DefaultLimitCORE")] + fn default_limit_core(&self) -> zbus::Result; + + /// DefaultLimitCORESoft property + #[zbus(property, name = "DefaultLimitCORESoft")] + fn default_limit_coresoft(&self) -> zbus::Result; + + /// DefaultLimitCPU property + #[zbus(property, name = "DefaultLimitCPU")] + fn default_limit_cpu(&self) -> zbus::Result; + + /// DefaultLimitCPUSoft property + #[zbus(property, name = "DefaultLimitCPUSoft")] + fn default_limit_cpusoft(&self) -> zbus::Result; + + /// DefaultLimitDATA property + #[zbus(property, name = "DefaultLimitDATA")] + fn default_limit_data(&self) -> zbus::Result; + + /// DefaultLimitDATASoft property + #[zbus(property, name = "DefaultLimitDATASoft")] + fn default_limit_datasoft(&self) -> zbus::Result; + + /// DefaultLimitFSIZE property + #[zbus(property, name = "DefaultLimitFSIZE")] + fn default_limit_fsize(&self) -> zbus::Result; + + /// DefaultLimitFSIZESoft property + #[zbus(property, name = "DefaultLimitFSIZESoft")] + fn default_limit_fsizesoft(&self) -> zbus::Result; + + /// DefaultLimitLOCKS property + #[zbus(property, name = "DefaultLimitLOCKS")] + fn default_limit_locks(&self) -> zbus::Result; + + /// DefaultLimitLOCKSSoft property + #[zbus(property, name = "DefaultLimitLOCKSSoft")] + fn default_limit_lockssoft(&self) -> zbus::Result; + + /// DefaultLimitMEMLOCK property + #[zbus(property, name = "DefaultLimitMEMLOCK")] + fn default_limit_memlock(&self) -> zbus::Result; + + /// DefaultLimitMEMLOCKSoft property + #[zbus(property, name = "DefaultLimitMEMLOCKSoft")] + fn default_limit_memlocksoft(&self) -> zbus::Result; + + /// DefaultLimitMSGQUEUE property + #[zbus(property, name = "DefaultLimitMSGQUEUE")] + fn default_limit_msgqueue(&self) -> zbus::Result; + + /// DefaultLimitMSGQUEUESoft property + #[zbus(property, name = "DefaultLimitMSGQUEUESoft")] + fn default_limit_msgqueuesoft(&self) -> zbus::Result; + + /// DefaultLimitNICE property + #[zbus(property, name = "DefaultLimitNICE")] + fn default_limit_nice(&self) -> zbus::Result; + + /// DefaultLimitNICESoft property + #[zbus(property, name = "DefaultLimitNICESoft")] + fn default_limit_nicesoft(&self) -> zbus::Result; + + /// DefaultLimitNOFILE property + #[zbus(property, name = "DefaultLimitNOFILE")] + fn default_limit_nofile(&self) -> zbus::Result; + + /// DefaultLimitNOFILESoft property + #[zbus(property, name = "DefaultLimitNOFILESoft")] + fn default_limit_nofilesoft(&self) -> zbus::Result; + + /// DefaultLimitNPROC property + #[zbus(property, name = "DefaultLimitNPROC")] + fn default_limit_nproc(&self) -> zbus::Result; + + /// DefaultLimitNPROCSoft property + #[zbus(property, name = "DefaultLimitNPROCSoft")] + fn default_limit_nprocsoft(&self) -> zbus::Result; + + /// DefaultLimitRSS property + #[zbus(property, name = "DefaultLimitRSS")] + fn default_limit_rss(&self) -> zbus::Result; + + /// DefaultLimitRSSSoft property + #[zbus(property, name = "DefaultLimitRSSSoft")] + fn default_limit_rsssoft(&self) -> zbus::Result; + + /// DefaultLimitRTPRIO property + #[zbus(property, name = "DefaultLimitRTPRIO")] + fn default_limit_rtprio(&self) -> zbus::Result; + + /// DefaultLimitRTPRIOSoft property + #[zbus(property, name = "DefaultLimitRTPRIOSoft")] + fn default_limit_rtpriosoft(&self) -> zbus::Result; + + /// DefaultLimitRTTIME property + #[zbus(property, name = "DefaultLimitRTTIME")] + fn default_limit_rttime(&self) -> zbus::Result; + + /// DefaultLimitRTTIMESoft property + #[zbus(property, name = "DefaultLimitRTTIMESoft")] + fn default_limit_rttimesoft(&self) -> zbus::Result; + + /// DefaultLimitSIGPENDING property + #[zbus(property, name = "DefaultLimitSIGPENDING")] + fn default_limit_sigpending(&self) -> zbus::Result; + + /// DefaultLimitSIGPENDINGSoft property + #[zbus(property, name = "DefaultLimitSIGPENDINGSoft")] + fn default_limit_sigpendingsoft(&self) -> zbus::Result; + + /// DefaultLimitSTACK property + #[zbus(property, name = "DefaultLimitSTACK")] + fn default_limit_stack(&self) -> zbus::Result; + + /// DefaultLimitSTACKSoft property + #[zbus(property, name = "DefaultLimitSTACKSoft")] + fn default_limit_stacksoft(&self) -> zbus::Result; + + /// DefaultMemoryAccounting property + #[zbus(property)] + fn default_memory_accounting(&self) -> zbus::Result; + + /// DefaultOOMPolicy property + #[zbus(property, name = "DefaultOOMPolicy")] + fn default_oompolicy(&self) -> zbus::Result; + + /// DefaultRestartUSec property + #[zbus(property, name = "DefaultRestartUSec")] + fn default_restart_usec(&self) -> zbus::Result; + + /// DefaultStandardError property + #[zbus(property)] + fn default_standard_error(&self) -> zbus::Result; + + /// DefaultStandardOutput property + #[zbus(property)] + fn default_standard_output(&self) -> zbus::Result; + + /// DefaultStartLimitBurst property + #[zbus(property)] + fn default_start_limit_burst(&self) -> zbus::Result; + + /// DefaultStartLimitIntervalUSec property + #[zbus(property, name = "DefaultStartLimitIntervalUSec")] + fn default_start_limit_interval_usec(&self) -> zbus::Result; + + /// DefaultTasksAccounting property + #[zbus(property)] + fn default_tasks_accounting(&self) -> zbus::Result; + + /// DefaultTasksMax property + #[zbus(property)] + fn default_tasks_max(&self) -> zbus::Result; + + /// DefaultTimeoutAbortUSec property + #[zbus(property, name = "DefaultTimeoutAbortUSec")] + fn default_timeout_abort_usec(&self) -> zbus::Result; + + /// DefaultTimeoutStartUSec property + #[zbus(property, name = "DefaultTimeoutStartUSec")] + fn default_timeout_start_usec(&self) -> zbus::Result; + + /// DefaultTimeoutStopUSec property + #[zbus(property, name = "DefaultTimeoutStopUSec")] + fn default_timeout_stop_usec(&self) -> zbus::Result; + + /// DefaultTimerAccuracyUSec property + #[zbus(property, name = "DefaultTimerAccuracyUSec")] + fn default_timer_accuracy_usec(&self) -> zbus::Result; + + /// Environment property + #[zbus(property)] + fn environment(&self) -> zbus::Result>; + + /// ExitCode property + #[zbus(property)] + fn exit_code(&self) -> zbus::Result; + + /// Features property + #[zbus(property)] + fn features(&self) -> zbus::Result; + + /// FinishTimestamp property + #[zbus(property)] + fn finish_timestamp(&self) -> zbus::Result; + + /// FinishTimestampMonotonic property + #[zbus(property)] + fn finish_timestamp_monotonic(&self) -> zbus::Result; + + /// FirmwareTimestamp property + #[zbus(property)] + fn firmware_timestamp(&self) -> zbus::Result; + + /// FirmwareTimestampMonotonic property + #[zbus(property)] + fn firmware_timestamp_monotonic(&self) -> zbus::Result; + + /// GeneratorsFinishTimestamp property + #[zbus(property)] + fn generators_finish_timestamp(&self) -> zbus::Result; + + /// GeneratorsFinishTimestampMonotonic property + #[zbus(property)] + fn generators_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// GeneratorsStartTimestamp property + #[zbus(property)] + fn generators_start_timestamp(&self) -> zbus::Result; + + /// GeneratorsStartTimestampMonotonic property + #[zbus(property)] + fn generators_start_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDGeneratorsFinishTimestamp property + #[zbus(property, name = "InitRDGeneratorsFinishTimestamp")] + fn init_rdgenerators_finish_timestamp(&self) -> zbus::Result; + + /// InitRDGeneratorsFinishTimestampMonotonic property + #[zbus(property, name = "InitRDGeneratorsFinishTimestampMonotonic")] + fn init_rdgenerators_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDGeneratorsStartTimestamp property + #[zbus(property, name = "InitRDGeneratorsStartTimestamp")] + fn init_rdgenerators_start_timestamp(&self) -> zbus::Result; + + /// InitRDGeneratorsStartTimestampMonotonic property + #[zbus(property, name = "InitRDGeneratorsStartTimestampMonotonic")] + fn init_rdgenerators_start_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDSecurityFinishTimestamp property + #[zbus(property, name = "InitRDSecurityFinishTimestamp")] + fn init_rdsecurity_finish_timestamp(&self) -> zbus::Result; + + /// InitRDSecurityFinishTimestampMonotonic property + #[zbus(property, name = "InitRDSecurityFinishTimestampMonotonic")] + fn init_rdsecurity_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDSecurityStartTimestamp property + #[zbus(property, name = "InitRDSecurityStartTimestamp")] + fn init_rdsecurity_start_timestamp(&self) -> zbus::Result; + + /// InitRDSecurityStartTimestampMonotonic property + #[zbus(property, name = "InitRDSecurityStartTimestampMonotonic")] + fn init_rdsecurity_start_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDTimestamp property + #[zbus(property, name = "InitRDTimestamp")] + fn init_rdtimestamp(&self) -> zbus::Result; + + /// InitRDTimestampMonotonic property + #[zbus(property, name = "InitRDTimestampMonotonic")] + fn init_rdtimestamp_monotonic(&self) -> zbus::Result; + + /// InitRDUnitsLoadFinishTimestamp property + #[zbus(property, name = "InitRDUnitsLoadFinishTimestamp")] + fn init_rdunits_load_finish_timestamp(&self) -> zbus::Result; + + /// InitRDUnitsLoadFinishTimestampMonotonic property + #[zbus(property, name = "InitRDUnitsLoadFinishTimestampMonotonic")] + fn init_rdunits_load_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// InitRDUnitsLoadStartTimestamp property + #[zbus(property, name = "InitRDUnitsLoadStartTimestamp")] + fn init_rdunits_load_start_timestamp(&self) -> zbus::Result; + + /// InitRDUnitsLoadStartTimestampMonotonic property + #[zbus(property, name = "InitRDUnitsLoadStartTimestampMonotonic")] + fn init_rdunits_load_start_timestamp_monotonic(&self) -> zbus::Result; + + /// KExecWatchdogUSec property + #[zbus(property, name = "KExecWatchdogUSec")] + fn kexec_watchdog_usec(&self) -> zbus::Result; + #[zbus(property, name = "KExecWatchdogUSec")] + fn set_kexec_watchdog_usec(&self, value: u64) -> zbus::Result<()>; + + /// KernelTimestamp property + #[zbus(property)] + fn kernel_timestamp(&self) -> zbus::Result; + + /// KernelTimestampMonotonic property + #[zbus(property)] + fn kernel_timestamp_monotonic(&self) -> zbus::Result; + + /// LoaderTimestamp property + #[zbus(property)] + fn loader_timestamp(&self) -> zbus::Result; + + /// LoaderTimestampMonotonic property + #[zbus(property)] + fn loader_timestamp_monotonic(&self) -> zbus::Result; + + /// LogLevel property + #[zbus(property)] + fn log_level(&self) -> zbus::Result; + #[zbus(property)] + fn set_log_level(&self, value: &str) -> zbus::Result<()>; + + /// LogTarget property + #[zbus(property)] + fn log_target(&self) -> zbus::Result; + #[zbus(property)] + fn set_log_target(&self, value: &str) -> zbus::Result<()>; + + /// NFailedJobs property + #[zbus(property, name = "NFailedJobs")] + fn nfailed_jobs(&self) -> zbus::Result; + + /// NFailedUnits property + #[zbus(property, name = "NFailedUnits")] + fn nfailed_units(&self) -> zbus::Result; + + /// NInstalledJobs property + #[zbus(property, name = "NInstalledJobs")] + fn ninstalled_jobs(&self) -> zbus::Result; + + /// NJobs property + #[zbus(property, name = "NJobs")] + fn njobs(&self) -> zbus::Result; + + /// NNames property + #[zbus(property, name = "NNames")] + fn nnames(&self) -> zbus::Result; + + /// Progress property + #[zbus(property)] + fn progress(&self) -> zbus::Result; + + /// RebootWatchdogUSec property + #[zbus(property, name = "RebootWatchdogUSec")] + fn reboot_watchdog_usec(&self) -> zbus::Result; + #[zbus(property, name = "RebootWatchdogUSec")] + fn set_reboot_watchdog_usec(&self, value: u64) -> zbus::Result<()>; + + /// RuntimeWatchdogUSec property + #[zbus(property, name = "RuntimeWatchdogUSec")] + fn runtime_watchdog_usec(&self) -> zbus::Result; + #[zbus(property, name = "RuntimeWatchdogUSec")] + fn set_runtime_watchdog_usec(&self, value: u64) -> zbus::Result<()>; + + /// SecurityFinishTimestamp property + #[zbus(property)] + fn security_finish_timestamp(&self) -> zbus::Result; + + /// SecurityFinishTimestampMonotonic property + #[zbus(property)] + fn security_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// SecurityStartTimestamp property + #[zbus(property)] + fn security_start_timestamp(&self) -> zbus::Result; + + /// SecurityStartTimestampMonotonic property + #[zbus(property)] + fn security_start_timestamp_monotonic(&self) -> zbus::Result; + + /// ServiceWatchdogs property + #[zbus(property)] + fn service_watchdogs(&self) -> zbus::Result; + #[zbus(property)] + fn set_service_watchdogs(&self, value: bool) -> zbus::Result<()>; + + /// ShowStatus property + #[zbus(property)] + fn show_status(&self) -> zbus::Result; + + /// SystemState property + #[zbus(property)] + fn system_state(&self) -> zbus::Result; + + /// Tainted property + #[zbus(property)] + fn tainted(&self) -> zbus::Result; + + /// TimerSlackNSec property + #[zbus(property, name = "TimerSlackNSec")] + fn timer_slack_nsec(&self) -> zbus::Result; + + /// UnitPath property + #[zbus(property)] + fn unit_path(&self) -> zbus::Result>; + + /// UnitsLoadFinishTimestamp property + #[zbus(property)] + fn units_load_finish_timestamp(&self) -> zbus::Result; + + /// UnitsLoadFinishTimestampMonotonic property + #[zbus(property)] + fn units_load_finish_timestamp_monotonic(&self) -> zbus::Result; + + /// UnitsLoadStartTimestamp property + #[zbus(property)] + fn units_load_start_timestamp(&self) -> zbus::Result; + + /// UnitsLoadStartTimestampMonotonic property + #[zbus(property)] + fn units_load_start_timestamp_monotonic(&self) -> zbus::Result; + + /// UserspaceTimestamp property + #[zbus(property)] + fn userspace_timestamp(&self) -> zbus::Result; + + /// UserspaceTimestampMonotonic property + #[zbus(property)] + fn userspace_timestamp_monotonic(&self) -> zbus::Result; + + /// Version property + #[zbus(property)] + fn version(&self) -> zbus::Result; + + /// Virtualization property + #[zbus(property)] + fn virtualization(&self) -> zbus::Result; +} diff --git a/src/systemd/error.rs b/src/systemd/error.rs new file mode 100644 index 0000000..9189174 --- /dev/null +++ b/src/systemd/error.rs @@ -0,0 +1,18 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invalid argument")] + InvalidArgument, + + #[error("obsolete systemd, please upgrade your systemd")] + ObsoleteSystemd, + + #[error("resource not supported by cgroups v1")] + CgroupsV1NotSupported, +} diff --git a/src/systemd/memory.rs b/src/systemd/memory.rs new file mode 100644 index 0000000..66c6cd2 --- /dev/null +++ b/src/systemd/memory.rs @@ -0,0 +1,32 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use crate::systemd::error::{Error, Result}; +use crate::systemd::{MEMORY_LIMIT, MEMORY_LOW, MEMORY_MAX, MEMORY_SWAP_MAX}; + +/// Returns the property for memory limit. +pub fn limit(limit: i64, v2: bool) -> Result<(&'static str, u64)> { + let id = if v2 { MEMORY_MAX } else { MEMORY_LIMIT }; + + Ok((id, limit as u64)) +} + +/// Returns the property for memory limit. +pub fn low(low: i64, v2: bool) -> Result<(&'static str, u64)> { + if !v2 { + return Err(Error::CgroupsV1NotSupported); + } + + Ok((MEMORY_LOW, low as u64)) +} + +/// Returns the property for memory swap. +pub fn swap(swap: i64, v2: bool) -> Result<(&'static str, u64)> { + if !v2 { + return Err(Error::CgroupsV1NotSupported); + } + + Ok((MEMORY_SWAP_MAX, swap as u64)) +} diff --git a/src/systemd/mod.rs b/src/systemd/mod.rs new file mode 100644 index 0000000..66e35eb --- /dev/null +++ b/src/systemd/mod.rs @@ -0,0 +1,25 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +pub mod cpu; +pub mod cpuset; +pub mod dbus; +pub use dbus::SystemdClient; +mod consts; +pub use consts::*; +pub mod error; +pub mod memory; +pub mod pids; +pub mod props; +pub use props::Property; +pub mod utils; + +pub const DEFAULT_SLICE: &str = "system.slice"; + +pub const SLICE_SUFFIX: &str = ".slice"; +pub const SCOPE_SUFFIX: &str = ".scope"; + +pub const CPU_SYSTEMD_VERSION: usize = 242; +pub const CPUSET_SYSTEMD_VERSION: usize = 244; diff --git a/src/systemd/pids.rs b/src/systemd/pids.rs new file mode 100644 index 0000000..5dab66c --- /dev/null +++ b/src/systemd/pids.rs @@ -0,0 +1,11 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use crate::systemd::error::Result; +use crate::systemd::TASKS_MAX; + +pub fn max(max: i64) -> Result<(&'static str, u64)> { + Ok((TASKS_MAX, max as u64)) +} diff --git a/src/systemd/props.rs b/src/systemd/props.rs new file mode 100644 index 0000000..8b6f8e2 --- /dev/null +++ b/src/systemd/props.rs @@ -0,0 +1,170 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use zbus::zvariant::Value as ZbusValue; + +use crate::fs::hierarchies; +use crate::systemd::utils::is_slice_unit; +use crate::systemd::{ + BLOCK_IO_ACCOUNTING, CPU_ACCOUNTING, DEFAULT_DEPENDENCIES, DEFAULT_DESCRIPTION, DELEGATE, + DESCRIPTION, IO_ACCOUNTING, MEMORY_ACCOUNTING, PIDS, SLICE, TASKS_ACCOUNTING, + TIMEOUT_STOP_USEC, WANTS, +}; + +pub type Property<'a> = (&'a str, ZbusValue<'a>); + +#[derive(Debug, Clone, Default)] +pub struct PropertiesBuilder { + cpu_accounting: Option, + // MemoryAccount is for cgroup v2 as documented in dbus. However, + // "github.com/opencontainer/runc" uses it for all. Shall we follow the + // same way? + memory_accounting: Option, + task_accounting: Option, + // Use IO_ACCOUNTING for cgroup v2 and BLOCK_IO_ACCOUNTING for cgroup v1. + io_accounting: Option, + default_dependencies: Option, + description: Option, + wants: Option, + slice: Option, + delegate: Option, + pids: Option>, + timeout_stop_usec: Option, +} + +impl PropertiesBuilder { + pub fn default_cgroup(slice: &str, unit: &str) -> Self { + let mut builder = Self::default() + .cpu_accounting(true) + .memory_accounting(true) + .task_accounting(true) + .io_accounting(true) + .default_dependencies(false) + .description(format!("{} {}:{}", DEFAULT_DESCRIPTION, slice, unit)); + + if is_slice_unit(unit) { + // If we create a slice, the parent is defined via a Wants=. + builder = builder.wants(slice.to_string()); + } else { + // Otherwise it's a scope, which we put into a Slice=. + builder = builder.slice(slice.to_string()); + // Assume scopes always support delegation (supported since systemd v218). + builder = builder.delegate(true); + } + + builder + } + + pub fn cpu_accounting(mut self, enabled: bool) -> Self { + self.cpu_accounting = Some(enabled); + self + } + + pub fn memory_accounting(mut self, enabled: bool) -> Self { + self.memory_accounting = Some(enabled); + self + } + + pub fn task_accounting(mut self, enabled: bool) -> Self { + self.task_accounting = Some(enabled); + self + } + + pub fn io_accounting(mut self, enabled: bool) -> Self { + self.io_accounting = Some(enabled); + self + } + + pub fn default_dependencies(mut self, enabled: bool) -> Self { + self.default_dependencies = Some(enabled); + self + } + + pub fn description(mut self, desc: String) -> Self { + self.description = Some(desc); + self + } + + pub fn wants(mut self, wants: String) -> Self { + self.wants = Some(wants); + self + } + + pub fn slice(mut self, slice: String) -> Self { + self.slice = Some(slice); + self + } + + pub fn delegate(mut self, enabled: bool) -> Self { + self.delegate = Some(enabled); + self + } + + pub fn pids(mut self, pids: Vec) -> Self { + self.pids = Some(pids); + self + } + + pub fn timeout_stop_usec(mut self, timeout: u64) -> Self { + self.timeout_stop_usec = Some(timeout); + self + } + + pub fn build(self) -> Vec> { + let mut props = vec![]; + + if let Some(cpu_accounting) = self.cpu_accounting { + props.push((CPU_ACCOUNTING, ZbusValue::Bool(cpu_accounting))); + } + + if let Some(memory_accounting) = self.memory_accounting { + props.push((MEMORY_ACCOUNTING, ZbusValue::Bool(memory_accounting))); + } + + if let Some(task_accounting) = self.task_accounting { + props.push((TASKS_ACCOUNTING, ZbusValue::Bool(task_accounting))); + } + + if let Some(io_accounting) = self.io_accounting { + if hierarchies::is_cgroup2_unified_mode() { + props.push((IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); + } else { + props.push((BLOCK_IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); + } + } + + if let Some(default_dependencies) = self.default_dependencies { + props.push((DEFAULT_DEPENDENCIES, ZbusValue::Bool(default_dependencies))); + } + + if let Some(description) = self.description { + props.push((DESCRIPTION, ZbusValue::Str(description.into()))); + } else { + props.push((DESCRIPTION, ZbusValue::Str(DEFAULT_DESCRIPTION.into()))); + } + + if let Some(wants) = self.wants { + props.push((WANTS, ZbusValue::Str(wants.into()))); + } + + if let Some(slice) = self.slice { + props.push((SLICE, ZbusValue::Str(slice.into()))); + } + + if let Some(delegate) = self.delegate { + props.push((DELEGATE, ZbusValue::Bool(delegate))); + } + + if let Some(pids) = self.pids { + props.push((PIDS, ZbusValue::Array(pids.into()))); + } + + if let Some(timeout) = self.timeout_stop_usec { + props.push((TIMEOUT_STOP_USEC, ZbusValue::U64(timeout))); + } + + props + } +} diff --git a/src/systemd/utils.rs b/src/systemd/utils.rs new file mode 100644 index 0000000..c654c59 --- /dev/null +++ b/src/systemd/utils.rs @@ -0,0 +1,64 @@ +// Copyright (c) 2025 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +use crate::systemd::error::{Error, Result}; +use crate::systemd::{SCOPE_SUFFIX, SLICE_SUFFIX}; + +/// Check if a systemd unit name is a slice unit. +pub fn is_slice_unit(name: &str) -> bool { + name.ends_with(SLICE_SUFFIX) +} + +/// Check if a systemd unit name is a scope unit. +pub fn is_scope_unit(name: &str) -> bool { + name.ends_with(SCOPE_SUFFIX) +} + +/// Expand a slice name to a full path in the filesystem. +/// +/// # Arguments +/// +/// * `slice` - A string slice that holds the slice name in the format +/// "xxx-yyy-zzz.slice". +/// +/// # Returns +/// +/// A string that represents the full path of the slice in the filesystem. +/// In the above case, the value would be +/// "xxx.slice/xxx-yyy.slice/xxx-yyy-zzz.slice". +pub fn expand_slice(slice: &str) -> Result { + // Name has to end with ".slice", but can't be just ".slice". + if !slice.ends_with(SLICE_SUFFIX) || slice.len() < SLICE_SUFFIX.len() { + return Err(Error::InvalidArgument); + } + + // Path-separators are not allowed. + if slice.contains('/') { + return Err(Error::InvalidArgument); + } + + let name = slice.trim_end_matches(SLICE_SUFFIX); + + // If input was -.slice, we should just return root now + if name == "-" { + return Ok("".to_string()); + } + + let mut slice_path = String::new(); + let mut prefix = String::new(); + for sub_slice in name.split('-') { + if sub_slice.is_empty() { + return Err(Error::InvalidArgument); + } + + slice_path = format!("{}/{}{}{}", slice_path, prefix, sub_slice, SLICE_SUFFIX); + prefix = format!("{}{}-", prefix, sub_slice); + } + + // We need a relative path, so remove the first slash. + slice_path.remove(0); + + Ok(slice_path) +}