diff --git a/Cargo.toml b/Cargo.toml index a644294..bd02317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4" nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] } libc = "0.2" serde = { version = "1.0", features = ["derive"], optional = true } +serde_json = { version = "1.0", optional = true } thiserror = "1" oci-spec = { version = "0.8.1", optional = true } zbus = "5.8" @@ -28,4 +29,4 @@ nix = "0.25" [features] default = [] -oci = ["oci-spec"] +oci = ["oci-spec", "serde", "serde_json"] diff --git a/src/lib.rs b/src/lib.rs index 362f4b8..1d29650 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub enum FreezerState { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub struct CgroupPid { /// The process identifier - pub pid: u64, + pid: u64, } impl From for CgroupPid { @@ -49,6 +49,28 @@ impl From<&std::process::Child> for CgroupPid { } } +impl From for CgroupPid { + fn from(u: u32) -> CgroupPid { + CgroupPid { pid: u as u64 } + } +} + +impl From for CgroupPid { + fn from(u: i32) -> CgroupPid { + CgroupPid { pid: u as u64 } + } +} + +impl CgroupPid { + pub fn set(&mut self, pid: u64) { + self.pid = pid; + } + + pub fn as_raw(&self) -> u64 { + self.pid + } +} + #[cfg(test)] pub mod tests { use std::fs; diff --git a/src/manager/error.rs b/src/manager/error.rs index 132dfd0..8027e63 100644 --- a/src/manager/error.rs +++ b/src/manager/error.rs @@ -25,4 +25,7 @@ pub enum Error { #[error("systemd dbus error: {0}")] SystemdDbus(#[from] SystemdDbusError), + + #[error("serialization error: {0}")] + Serialization(#[from] serde_json::Error), } diff --git a/src/manager/fs.rs b/src/manager/fs.rs index f865594..81600bd 100644 --- a/src/manager/fs.rs +++ b/src/manager/fs.rs @@ -12,6 +12,7 @@ use oci_spec::runtime::{ LinuxBlockIo, LinuxCpu, LinuxDeviceCgroup, LinuxHugepageLimit, LinuxMemory, LinuxNetwork, LinuxPids, LinuxResources, }; +use serde::{Deserialize, Serialize}; use crate::fs::blkio::{BlkIoController, BlkIoData, IoService, IoStat}; use crate::fs::cgroup::UNIFIED_MOUNTPOINT; @@ -31,7 +32,8 @@ use crate::manager::error::Error; use crate::manager::{conv, Manager, Result}; use crate::stats::{ BlkioCgroupStats, BlkioStat, CpuAcctStats, CpuCgroupStats, CpuThrottlingStats, - HugeTlbCgroupStats, HugeTlbStat, MemoryCgroupStats, MemoryStats, PidsCgroupStats, + DeviceCgroupStat, DevicesCgroupStats, HugeTlbCgroupStats, HugeTlbStat, MemoryCgroupStats, + MemoryStats, PidsCgroupStats, }; use crate::{CgroupPid, CgroupStats, FreezerState}; @@ -42,7 +44,7 @@ const MOUNTINFO_PATH: &str = "/proc/self/mountinfo"; /// /// This manager deals with `LinuxResources` conformed to the OCI runtime /// specification, so that it allows users not to do type conversions. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct FsManager { /// Cgroup subsystem paths read from `/proc/self/cgroup` /// - cgroup v1: -> @@ -55,6 +57,7 @@ pub struct FsManager { /// - cgroup v2: "/sys/fs/cgroup/" base: String, /// Cgroup managed by this manager. + #[serde(skip)] cgroup: Cgroup, } @@ -83,7 +86,7 @@ impl FsManager { impl FsManager { /// Create the cgroups if they are not created yet. - pub(crate) fn create_cgroups(&mut self) -> Result<()> { + pub fn create_cgroups(&mut self) -> Result<()> { if self.exists() { return Ok(()); } @@ -128,7 +131,10 @@ impl FsManager { } fn set_cpuset(&self, linux_cpu: &LinuxCpu) -> Result<()> { - let controller: &CpuSetController = self.controller()?; + let controller: &CpuSetController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; if let Some(cpus) = linux_cpu.cpus() { controller.set_cpus(cpus)?; @@ -142,7 +148,10 @@ impl FsManager { } fn set_cpu(&self, linux_cpu: &LinuxCpu) -> Result<()> { - let controller: &CpuController = self.controller()?; + let controller: &CpuController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; if let Some(shares) = linux_cpu.shares() { let shares = if self.v2() { @@ -210,7 +219,10 @@ impl FsManager { } fn set_memory_v1(&self, linux_memory: &LinuxMemory) -> Result<()> { - let controller: &MemController = self.controller()?; + let controller: &MemController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; let mem_limit = linux_memory.limit().unwrap_or(0); let memswap_limit = linux_memory.swap().unwrap_or(0); @@ -237,7 +249,10 @@ impl FsManager { } fn set_memory_v2(&self, linux_memory: &LinuxMemory) -> Result<()> { - let controller: &MemController = self.controller()?; + let controller: &MemController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; if linux_memory.reservation().is_none() && linux_memory.limit().is_none() @@ -297,7 +312,11 @@ impl FsManager { } fn set_pids(&self, pids: &LinuxPids) -> Result<()> { - let controller: &PidController = self.controller()?; + let controller: &PidController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; + let value = if pids.limit() > 0 { MaxValue::Value(pids.limit()) } else { @@ -309,7 +328,10 @@ impl FsManager { } fn set_blkio(&self, blkio: &LinuxBlockIo) -> Result<()> { - let controller: &BlkIoController = self.controller()?; + let controller: &BlkIoController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; if let Some(weight) = blkio.weight() { controller.set_weight(weight as u64)?; @@ -372,7 +394,10 @@ impl FsManager { } fn set_hugepages(&self, hugepage_limits: &[LinuxHugepageLimit]) -> Result<()> { - let controller: &HugeTlbController = self.controller()?; + let controller: &HugeTlbController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; for limit in hugepage_limits.iter() { // ignore not supported page size @@ -389,16 +414,18 @@ impl FsManager { fn set_network(&self, network: &LinuxNetwork) -> Result<()> { if let Some(class_id) = network.class_id() { - let controller: &NetClsController = self.controller()?; - controller.set_class(class_id as u64)?; + if let Ok(controller) = self.controller::() { + controller.set_class(class_id as u64)?; + } } if let Some(priorities) = network.priorities() { - let controller: &NetPrioController = self.controller()?; - for priority in priorities.iter() { - let eif = priority.name(); - let prio = priority.priority() as u64; - controller.set_if_prio(eif, prio)?; + if let Ok(controller) = self.controller::() { + for priority in priorities.iter() { + let eif = priority.name(); + let prio = priority.priority() as u64; + controller.set_if_prio(eif, prio)?; + } } } @@ -406,7 +433,10 @@ impl FsManager { } fn set_devices(&self, devices: &[LinuxDeviceCgroup]) -> Result<()> { - let controller: &DevicesController = self.controller()?; + let controller: &DevicesController = match self.controller() { + Ok(c) => c, + Err(_) => return Ok(()), + }; for device in devices.iter() { let devtype = @@ -725,6 +755,41 @@ impl FsManager { }) .collect() } + + fn devices_cgroup_stats(&self) -> DevicesCgroupStats { + let controller: &DevicesController = match self.controller() { + Ok(controller) => controller, + Err(_) => return DevicesCgroupStats::default(), + }; + + let list = controller + .allowed_devices() + .map(|devs| { + devs.iter() + .map(|dev| DeviceCgroupStat { + dev_type: dev.devtype.to_char().to_string(), + major: dev.major, + minor: dev.minor, + access: { + let mut access = String::new(); + if dev.access.contains(&DevicePermissions::Read) { + access.push('r'); + } + if dev.access.contains(&DevicePermissions::Write) { + access.push('w'); + } + if dev.access.contains(&DevicePermissions::MkNod) { + access.push('m'); + } + access + }, + }) + .collect::>() + }) + .unwrap_or_default(); + + DevicesCgroupStats { list } + } } impl Manager for FsManager { @@ -860,6 +925,7 @@ impl Manager for FsManager { pids: self.pids_cgroup_stats(), blkio: self.blkio_cgroup_stats(), hugetlb: self.huge_tlb_cgroup_stats(), + devices: self.devices_cgroup_stats(), } } @@ -871,6 +937,11 @@ impl Manager for FsManager { &self.mounts } + fn serialize(&self) -> Result { + let json = serde_json::to_string(self)?; + Ok(json) + } + fn systemd(&self) -> bool { false } diff --git a/src/manager/mod.rs b/src/manager/mod.rs index d7a0f9e..dd83777 100644 --- a/src/manager/mod.rs +++ b/src/manager/mod.rs @@ -4,8 +4,6 @@ // mod error; -use std::collections::HashMap; - pub use error::{Error, Result}; mod fs; pub use fs::FsManager; @@ -13,6 +11,9 @@ mod systemd; pub use systemd::SystemdManager; mod conv; +use std::collections::HashMap; +use std::fmt::Debug; + use oci_spec::runtime::LinuxResources; use crate::systemd::SLICE_SUFFIX; @@ -25,7 +26,7 @@ pub fn is_systemd_cgroup(cgroups_path: &str) -> bool { } /// Manage cgroups designed for OCI containers. -pub trait Manager: Send + Sync { +pub trait Manager: Send + Sync + Debug { /// Add a process specified by its tgid. fn add_proc(&mut self, tgid: CgroupPid) -> Result<()>; @@ -78,6 +79,9 @@ pub trait Manager: Send + Sync { /// mappings of relative paths see "paths()". fn mounts(&self) -> &HashMap; + /// Serialize the cgroup manager to a string in the format of JSON. + fn serialize(&self) -> Result; + /// Indicate whether the cgroup manager is using systemd. fn systemd(&self) -> bool; diff --git a/src/manager/systemd.rs b/src/manager/systemd.rs index ffb0140..d6a1d19 100644 --- a/src/manager/systemd.rs +++ b/src/manager/systemd.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use oci_spec::runtime::{LinuxCpu, LinuxMemory, LinuxPids, LinuxResources}; -use zbus::zvariant::Value as ZbusValue; +use serde::{Deserialize, Serialize}; use crate::manager::conv; use crate::manager::error::{Error, Result}; @@ -26,18 +26,19 @@ use crate::{CgroupPid, CgroupStats, FreezerState, Manager}; /// 2: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html const DEFAULT_CPU_QUOTA_PERIOD: u64 = 100_000; // 100ms -pub struct SystemdManager<'a> { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SystemdManager { /// The name of slice slice: String, /// The name of unit unit: String, /// Systemd client - systemd_client: SystemdClient<'a>, + systemd_client: SystemdClient, /// Cgroupfs manager fs_manager: FsManager, } -impl SystemdManager<'_> { +impl SystemdManager { fn parse_slice_and_unit(path: &str) -> Result<(String, String)> { let parts: Vec<&str> = path.split(':').collect(); if parts.len() != 3 { @@ -80,7 +81,7 @@ impl SystemdManager<'_> { } } -impl SystemdManager<'_> { +impl SystemdManager { /// Get the slice name. pub fn slice(&self) -> &str { &self.slice @@ -98,13 +99,13 @@ impl SystemdManager<'_> { systemd_version: usize, ) -> Result<()> { if let Some(cpus) = linux_cpu.cpus().as_ref() { - let (id, value) = cpuset::cpus(cpus, systemd_version)?; - props.push((id, value.into())); + let prop = cpuset::cpus(cpus, systemd_version)?; + props.push(prop); } if let Some(mems) = linux_cpu.mems().as_ref() { - let (id, value) = cpuset::mems(mems, systemd_version)?; - props.push((id, value.into())); + let prop = cpuset::mems(mems, systemd_version)?; + props.push(prop); } Ok(()) @@ -122,16 +123,16 @@ impl SystemdManager<'_> { } else { shares }; - let (id, value) = cpu::shares(shares, self.v2())?; - props.push((id, value.into())); + let prop = cpu::shares(shares, self.v2())?; + props.push(prop); } let period = linux_cpu.period().unwrap_or(0); let quota = linux_cpu.quota().unwrap_or(0); if period != 0 { - let (id, value) = cpu::period(period, systemd_version)?; - props.push((id, value.into())); + let prop = cpu::period(period, systemd_version)?; + props.push(prop); } if period != 0 || quota != 0 { @@ -153,8 +154,8 @@ impl SystemdManager<'_> { quota_systemd = (quota_systemd / ms_to_us(10) + 1) * ms_to_us(10); } } - let (id, value) = cpu::quota(quota_systemd)?; - props.push((id, value.into())); + let prop = cpu::quota(quota_systemd)?; + props.push(prop); } Ok(()) @@ -165,21 +166,21 @@ impl SystemdManager<'_> { let mem_limit = linux_memory.limit().unwrap_or(0); if mem_limit != 0 { - let (id, value) = memory::limit(mem_limit, v2)?; - props.push((id, value.into())); + let prop = memory::limit(mem_limit, v2)?; + props.push(prop); } let reservation = linux_memory.reservation().unwrap_or(0); if reservation != 0 && v2 { - let (id, value) = memory::low(reservation, v2)?; - props.push((id, value.into())); + let prop = memory::low(reservation, v2)?; + props.push(prop); } let memswap_limit = linux_memory.swap().unwrap_or(0); if memswap_limit != 0 && v2 { let memswap_limit = conv::memory_swap_to_cgroup_v2(memswap_limit, mem_limit)?; - let (id, value) = memory::swap(memswap_limit, v2)?; - props.push((id, value.into())); + let prop = memory::swap(memswap_limit, v2)?; + props.push(prop); } Ok(()) @@ -188,8 +189,8 @@ impl SystemdManager<'_> { fn set_pids(&self, props: &mut Vec, linux_pids: &LinuxPids) -> Result<()> { let limit = linux_pids.limit(); if limit == -1 || limit > 0 { - let (id, value) = pids::max(limit)?; - props.push((id, value.into())); + let prop = pids::max(limit)?; + props.push(prop); } Ok(()) @@ -205,13 +206,13 @@ impl SystemdManager<'_> { /// ``` pub fn set_term_timeout(&mut self, timeout_in_sec: u64) -> Result<()> { let timeout_in_usec = timeout_in_sec * 1_000_000; - let prop = (TIMEOUT_STOP_USEC, ZbusValue::U64(timeout_in_usec)); + let prop = (TIMEOUT_STOP_USEC.to_string(), timeout_in_usec.into()); self.systemd_client.set_properties(&[prop])?; Ok(()) } } -impl Manager for SystemdManager<'_> { +impl Manager for SystemdManager { fn add_proc(&mut self, pid: CgroupPid) -> Result<()> { if !self.systemd_client.exists() { self.systemd_client.set_pid_prop(pid)?; @@ -304,6 +305,11 @@ impl Manager for SystemdManager<'_> { self.fs_manager.mounts() } + fn serialize(&self) -> Result { + let json = serde_json::to_string(self)?; + Ok(json) + } + fn systemd(&self) -> bool { true } @@ -377,7 +383,7 @@ mod tests { ) } - fn new_systemd_manager<'a>() -> SystemdManager<'a> { + fn new_systemd_manager() -> SystemdManager { let (slice, scope_prefix, name) = new_cgroups_path(); SystemdManager::new(&format!("{}:{}:{}", slice, scope_prefix, name)).unwrap() } diff --git a/src/stats.rs b/src/stats.rs index 856cc25..09932f0 100644 --- a/src/stats.rs +++ b/src/stats.rs @@ -13,6 +13,7 @@ pub struct CgroupStats { pub pids: PidsCgroupStats, pub blkio: BlkioCgroupStats, pub hugetlb: HugeTlbCgroupStats, + pub devices: DevicesCgroupStats, } #[derive(Debug, Default)] @@ -154,3 +155,16 @@ pub struct HugeTlbStat { pub max_usage: u64, pub fail_cnt: u64, } + +#[derive(Debug, Default)] +pub struct DeviceCgroupStat { + pub dev_type: String, + pub major: i64, + pub minor: i64, + pub access: String, +} + +#[derive(Debug, Default)] +pub struct DevicesCgroupStats { + pub list: Vec, +} diff --git a/src/systemd/cpu.rs b/src/systemd/cpu.rs index 2256866..9192055 100644 --- a/src/systemd/cpu.rs +++ b/src/systemd/cpu.rs @@ -4,8 +4,10 @@ // use crate::systemd::error::{Error, Result}; +use crate::systemd::props::Value; use crate::systemd::{ - CPU_QUOTA_PERIOD_US, CPU_QUOTA_PER_SEC_US, CPU_SHARES, CPU_SYSTEMD_VERSION, CPU_WEIGHT, + Property, CPU_QUOTA_PERIOD_US, CPU_QUOTA_PER_SEC_US, CPU_SHARES, CPU_SYSTEMD_VERSION, + CPU_WEIGHT, }; /// Returns the property for CPU shares. @@ -14,22 +16,22 @@ use crate::systemd::{ /// 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)> { +pub fn shares(shares: u64, v2: bool) -> Result { let id = if v2 { CPU_WEIGHT } else { CPU_SHARES }; - Ok((id, shares)) + Ok((id.to_string(), Value::U64(shares))) } /// Returns the property for CPU period. -pub fn period(period: u64, systemd_version: usize) -> Result<(&'static str, u64)> { +pub fn period(period: u64, systemd_version: usize) -> Result { if systemd_version < CPU_SYSTEMD_VERSION { return Err(Error::ObsoleteSystemd); } - Ok((CPU_QUOTA_PERIOD_US, period)) + Ok((CPU_QUOTA_PERIOD_US.to_string(), Value::U64(period))) } /// Return the property for CPU quota. -pub fn quota(quota: u64) -> Result<(&'static str, u64)> { - Ok((CPU_QUOTA_PER_SEC_US, quota)) +pub fn quota(quota: u64) -> Result { + Ok((CPU_QUOTA_PER_SEC_US.to_string(), Value::U64(quota))) } diff --git a/src/systemd/cpuset.rs b/src/systemd/cpuset.rs index b11cbcc..0f837bc 100644 --- a/src/systemd/cpuset.rs +++ b/src/systemd/cpuset.rs @@ -6,30 +6,31 @@ use bit_vec::BitVec; use crate::systemd::error::{Error, Result}; -use crate::systemd::{ALLOWED_CPUS, ALLOWED_MEMORY_NODES, CPUSET_SYSTEMD_VERSION}; +use crate::systemd::props::Value; +use crate::systemd::{Property, 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)> { +pub fn cpus(cpus: &str, systemd_version: usize) -> Result { if systemd_version < CPUSET_SYSTEMD_VERSION { return Err(Error::ObsoleteSystemd); } let mask = convert_list_to_mask(cpus)?; - Ok((ALLOWED_CPUS, mask)) + Ok((ALLOWED_CPUS.to_string(), Value::ArrayU8(mask))) } /// Returns the property for cpuset memory nodes. -pub fn mems(mems: &str, systemd_version: usize) -> Result<(&'static str, Vec)> { +pub fn mems(mems: &str, systemd_version: usize) -> Result { if systemd_version < CPUSET_SYSTEMD_VERSION { return Err(Error::ObsoleteSystemd); } let mask = convert_list_to_mask(mems)?; - Ok((ALLOWED_MEMORY_NODES, mask)) + Ok((ALLOWED_MEMORY_NODES.to_string(), Value::ArrayU8(mask))) } /// Convert cpuset cpus/mems from the string in comma-separated list format diff --git a/src/systemd/dbus/client.rs b/src/systemd/dbus/client.rs index 8bbe245..783d456 100644 --- a/src/systemd/dbus/client.rs +++ b/src/systemd/dbus/client.rs @@ -4,22 +4,24 @@ // 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::props::{Value, ZbusProperty, ZbusPropertyRef}; use crate::systemd::{Property, NO_SUCH_UNIT, PIDS, UNIT_MODE_REPLACE}; use crate::CgroupPid; -pub struct SystemdClient<'a> { +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct SystemdClient { /// The name of the systemd unit (slice or scope) unit: String, - props: Vec>, + props: Vec, } -impl<'a> SystemdClient<'a> { - pub fn new(unit: &str, props: Vec>) -> Result { +impl SystemdClient { + pub fn new(unit: &str, props: Vec) -> Result { Ok(Self { unit: unit.to_string(), props, @@ -27,7 +29,7 @@ impl<'a> SystemdClient<'a> { } } -impl SystemdClient<'_> { +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 @@ -40,9 +42,8 @@ impl SystemdClient<'_> { 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)?; + if let Value::ArrayU32(arr) = &mut prop.1 { + arr.push(pid.pid as u32); return Ok(()); } // Invalid type of PIDs @@ -51,7 +52,7 @@ impl SystemdClient<'_> { } // If PIDS is not set, we create a new property. self.props - .push((PIDS, Value::Array(vec![pid.pid as u32].into()))); + .push((PIDS.to_string(), vec![pid.pid as u32].into())); Ok(()) } @@ -63,17 +64,22 @@ impl SystemdClient<'_> { /// 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) { + 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(); + let props: Vec> = self + .props + .iter() + .map(|(k, v)| (k.clone(), v.into())) + .collect(); + let props_ref: Vec> = + props.iter().map(|(k, v)| (k.as_str(), v)).collect(); + let props_ref_ref: Vec<&ZbusPropertyRef<'_>> = props_ref.iter().collect(); - sys_proxy.start_transient_unit(&self.unit, UNIT_MODE_REPLACE, &props_borrowed, &[])?; + sys_proxy.start_transient_unit(&self.unit, UNIT_MODE_REPLACE, &props_ref_ref, &[])?; Ok(()) } @@ -99,14 +105,13 @@ impl SystemdClient<'_> { } /// 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)?; + pub fn set_properties(&mut self, properties: &[Property]) -> Result<()> { + for new in properties { // 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; + if let Some(existing) = self.props.iter_mut().find(|p| p.0 == new.0) { + existing.1 = new.1.clone(); } else { - self.props.push((prop.0, new)); + self.props.push((new.0.clone(), new.1.clone())); } } @@ -117,10 +122,15 @@ impl SystemdClient<'_> { 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(); + let props: Vec> = properties + .iter() + .map(|(k, v)| (k.clone(), v.into())) + .collect(); + let props_ref: Vec> = + props.iter().map(|(k, v)| (k.as_str(), v)).collect(); + let props_ref_ref: Vec<&ZbusPropertyRef<'_>> = props_ref.iter().collect(); - sys_proxy.set_unit_properties(&self.unit, true, &props_borrowed)?; + sys_proxy.set_unit_properties(&self.unit, true, &props_ref_ref)?; Ok(()) } @@ -250,7 +260,7 @@ pub mod tests { fn start_default_cgroup(pid: CgroupPid, unit: &str) -> SystemdClient { let mut props = PropertiesBuilder::default_cgroup(TEST_SLICE, unit).build(); - props.push((PIDS, Value::Array(vec![pid.pid as u32].into()))); + props.push((PIDS.to_string(), vec![pid.pid as u32].into())); let cgroup = SystemdClient::new(unit, props).unwrap(); // Stop the unit if it exists. cgroup.stop().unwrap(); @@ -443,13 +453,14 @@ pub mod tests { ); let properties = [( - DESCRIPTION, - Value::Str("kata-container1 description".into()), + DESCRIPTION.to_string(), + "kata-container1 description".into(), )]; cgroup.set_properties(&properties).unwrap(); - assert!(cgroup.props.iter().any(|(k, v)| { - k == &DESCRIPTION && v == &Value::Str("kata-container1 description".into()) - })); + assert!(cgroup + .props + .iter() + .any(|(k, v)| { k == DESCRIPTION && v == &"kata-container1 description".into() })); let output = systemd_show(&cgroup.unit); assert!( diff --git a/src/systemd/memory.rs b/src/systemd/memory.rs index 66c6cd2..e4ac8dc 100644 --- a/src/systemd/memory.rs +++ b/src/systemd/memory.rs @@ -4,29 +4,30 @@ // use crate::systemd::error::{Error, Result}; -use crate::systemd::{MEMORY_LIMIT, MEMORY_LOW, MEMORY_MAX, MEMORY_SWAP_MAX}; +use crate::systemd::props::Value; +use crate::systemd::{Property, 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)> { +pub fn limit(limit: i64, v2: bool) -> Result { let id = if v2 { MEMORY_MAX } else { MEMORY_LIMIT }; - Ok((id, limit as u64)) + Ok((id.to_string(), Value::U64(limit as u64))) } /// Returns the property for memory limit. -pub fn low(low: i64, v2: bool) -> Result<(&'static str, u64)> { +pub fn low(low: i64, v2: bool) -> Result { if !v2 { return Err(Error::CgroupsV1NotSupported); } - Ok((MEMORY_LOW, low as u64)) + Ok((MEMORY_LOW.to_string(), Value::U64(low as u64))) } /// Returns the property for memory swap. -pub fn swap(swap: i64, v2: bool) -> Result<(&'static str, u64)> { +pub fn swap(swap: i64, v2: bool) -> Result { if !v2 { return Err(Error::CgroupsV1NotSupported); } - Ok((MEMORY_SWAP_MAX, swap as u64)) + Ok((MEMORY_SWAP_MAX.to_string(), Value::U64(swap as u64))) } diff --git a/src/systemd/pids.rs b/src/systemd/pids.rs index 5dab66c..ac7eb67 100644 --- a/src/systemd/pids.rs +++ b/src/systemd/pids.rs @@ -4,8 +4,9 @@ // use crate::systemd::error::Result; -use crate::systemd::TASKS_MAX; +use crate::systemd::props::Value; +use crate::systemd::{Property, TASKS_MAX}; -pub fn max(max: i64) -> Result<(&'static str, u64)> { - Ok((TASKS_MAX, max as u64)) +pub fn max(max: i64) -> Result { + Ok((TASKS_MAX.to_string(), Value::U64(max as u64))) } diff --git a/src/systemd/props.rs b/src/systemd/props.rs index 8b6f8e2..f9558d4 100644 --- a/src/systemd/props.rs +++ b/src/systemd/props.rs @@ -13,7 +13,73 @@ use crate::systemd::{ TIMEOUT_STOP_USEC, WANTS, }; -pub type Property<'a> = (&'a str, ZbusValue<'a>); +pub type Property = (String, Value); +pub type ZbusProperty<'a> = (String, ZbusValue<'a>); +pub type ZbusPropertyRef<'a> = (&'a str, &'a ZbusValue<'a>); + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum Value { + Bool(bool), + U64(u64), + ArrayU32(Vec), + ArrayU8(Vec), + String(String), +} + +impl From> for Value { + fn from(arr: Vec) -> Self { + Value::ArrayU8(arr) + } +} + +impl From> for Value { + fn from(arr: Vec) -> Self { + Value::ArrayU32(arr) + } +} + +impl From for Value { + fn from(value: u64) -> Self { + Value::U64(value) + } +} + +impl From<&str> for Value { + fn from(value: &str) -> Self { + Value::String(value.to_string()) + } +} + +impl From for Value { + fn from(value: String) -> Self { + Value::String(value) + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value::Bool(value) + } +} + +impl From for ZbusValue<'_> { + fn from(value: Value) -> Self { + match value { + Value::U64(u) => ZbusValue::U64(u), + Value::Bool(b) => ZbusValue::Bool(b), + Value::ArrayU8(arr) => ZbusValue::Array(arr.into()), + Value::ArrayU32(arr) => ZbusValue::Array(arr.into()), + Value::String(s) => ZbusValue::Str(s.into()), + } + } +} + +impl From<&Value> for ZbusValue<'_> { + fn from(value: &Value) -> Self { + value.clone().into() + } +} #[derive(Debug, Clone, Default)] pub struct PropertiesBuilder { @@ -112,57 +178,60 @@ impl PropertiesBuilder { self } - pub fn build(self) -> Vec> { + 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))); + props.push((CPU_ACCOUNTING.to_string(), cpu_accounting.into())); } if let Some(memory_accounting) = self.memory_accounting { - props.push((MEMORY_ACCOUNTING, ZbusValue::Bool(memory_accounting))); + props.push((MEMORY_ACCOUNTING.to_string(), memory_accounting.into())); } if let Some(task_accounting) = self.task_accounting { - props.push((TASKS_ACCOUNTING, ZbusValue::Bool(task_accounting))); + props.push((TASKS_ACCOUNTING.to_string(), task_accounting.into())); } if let Some(io_accounting) = self.io_accounting { if hierarchies::is_cgroup2_unified_mode() { - props.push((IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); + props.push((IO_ACCOUNTING.to_string(), io_accounting.into())); } else { - props.push((BLOCK_IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); + props.push((BLOCK_IO_ACCOUNTING.to_string(), io_accounting.into())); } } if let Some(default_dependencies) = self.default_dependencies { - props.push((DEFAULT_DEPENDENCIES, ZbusValue::Bool(default_dependencies))); + props.push(( + DEFAULT_DEPENDENCIES.to_string(), + default_dependencies.into(), + )); } if let Some(description) = self.description { - props.push((DESCRIPTION, ZbusValue::Str(description.into()))); + props.push((DESCRIPTION.to_string(), description.into())); } else { - props.push((DESCRIPTION, ZbusValue::Str(DEFAULT_DESCRIPTION.into()))); + props.push((DESCRIPTION.to_string(), DEFAULT_DESCRIPTION.into())); } if let Some(wants) = self.wants { - props.push((WANTS, ZbusValue::Str(wants.into()))); + props.push((WANTS.to_string(), wants.into())); } if let Some(slice) = self.slice { - props.push((SLICE, ZbusValue::Str(slice.into()))); + props.push((SLICE.to_string(), slice.into())); } if let Some(delegate) = self.delegate { - props.push((DELEGATE, ZbusValue::Bool(delegate))); + props.push((DELEGATE.to_string(), delegate.into())); } if let Some(pids) = self.pids { - props.push((PIDS, ZbusValue::Array(pids.into()))); + props.push((PIDS.to_string(), pids.into())); } if let Some(timeout) = self.timeout_stop_usec { - props.push((TIMEOUT_STOP_USEC, ZbusValue::U64(timeout))); + props.push((TIMEOUT_STOP_USEC.to_string(), timeout.into())); } props