use std::{marker::PhantomData, ops::Deref}; use serde::{Deserialize, Serialize, de}; use serde_with::{DisplayFromStr, json::JsonString, serde_as}; use tracing::info; use zbus::{proxy, zvariant}; use super::metadata::{PoolMetadata, Sectors}; // Wrapper over an Option that (de-)serializes as (present: bool, T), to // be able to work with Stratis APIs that use a struct of bool + T `(bT)`. #[derive(Debug)] pub struct OptionalByPair(Option); impl OptionalByPair { pub fn into_option(self) -> Option { self.0 } } impl Deref for OptionalByPair { type Target = Option; fn deref(&self) -> &Self::Target { &self.0 } } impl From> for OptionalByPair { fn from(value: Option) -> Self { OptionalByPair(value) } } impl Serialize for OptionalByPair { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match &self.0 { Some(x) => (true, x).serialize(serializer), None => (false, T::default()).serialize(serializer), } } } impl<'de, T: Deserialize<'de>> Deserialize<'de> for OptionalByPair { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct Visitor(PhantomData); impl<'de, T: Deserialize<'de>> de::Visitor<'de> for Visitor { type Value = OptionalByPair; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { write!(formatter, "a (bool, T) struct") } fn visit_seq(self, mut seq: A) -> Result where A: de::SeqAccess<'de>, { let value = if seq .next_element() .and_then(|x| x.ok_or(de::Error::invalid_length(0, &"2")))? { seq.next_element() .and_then(|x| x.ok_or(de::Error::invalid_length(1, &"2"))) .map(Some)? } else { seq.next_element::() .and_then(|x| x.ok_or(de::Error::invalid_length(1, &"2")))?; None }; let None = seq.next_element::()? else { return Err(de::Error::invalid_length(3, &"2")); }; Ok(OptionalByPair(value)) } } deserializer.deserialize_tuple(2, Visitor(PhantomData::)) } } impl zvariant::Type for OptionalByPair { const SIGNATURE: &'static zvariant::Signature = &zvariant::Signature::static_structure(&[bool::SIGNATURE, T::SIGNATURE]); } impl<'v, T: TryFrom, Error = zbus::Error>> TryFrom> for OptionalByPair { type Error = zbus::Error; fn try_from(value: zvariant::Value<'v>) -> zbus::Result { let (exists, inner) = value.downcast::<(zvariant::Value<'v>, zvariant::Value<'v>)>()?; let exists = exists.downcast::()?; if exists { inner.try_into().map(Some).map(Into::into) } else { Ok(None.into()) } } } impl<'v, T: TryFrom, Error = zbus::Error>> TryFrom for OptionalByPair { type Error = zbus::Error; fn try_from(value: zvariant::OwnedValue) -> zbus::Result { zvariant::Value::from(value).try_into() } } impl<'v, T: Default + zvariant::Type + Into>> From> for zvariant::Value<'v> { fn from(value: OptionalByPair) -> Self { match value.0 { Some(x) => (true, x).into(), None => (false, T::default()).into(), } } } #[serde_as] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize, zvariant::Type)] #[serde(transparent)] #[zvariant(signature = "s")] pub struct Bytes(#[serde_as(as = "DisplayFromStr")] pub u128); impl From for Bytes { fn from(value: Sectors) -> Self { Bytes(value.0 as u128 * 512) } } impl<'a> TryFrom> for Bytes { type Error = zbus::Error; fn try_from(value: zvariant::Value<'a>) -> Result { let s = zvariant::Str::try_from(value)?; s.as_str() .parse() .map(Bytes) .map_err(|_| zbus::Error::Failure(format!("byte count '{s}' is not a u128"))) } } impl TryFrom for Bytes { type Error = zbus::Error; fn try_from(value: zvariant::OwnedValue) -> Result { zvariant::Value::from(value).try_into() } } impl From for zvariant::Value<'_> { fn from(value: Bytes) -> Self { zvariant::Value::Str(value.0.to_string().into()) } } #[derive(Deserialize, zvariant::Type)] pub struct OperationResult { pub result: T, pub return_code: u16, pub return_message: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct Version { major: u8, minor: u8, patch: u8, } impl std::fmt::Display for Version { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } } impl TryFrom> for Version { type Error = zvariant::Error; fn try_from(value: zvariant::Value) -> Result { let s = String::try_from(value)?; let components = s .split(".") .map(str::parse::) .collect::, _>>() .map_err(|e| zvariant::Error::Message(format!("version component invalid: {e}")))?; if components.len() != 3 { return Err(zvariant::Error::Message(format!( "version expected 3 components, got {}", components.len() ))); } Ok(Version { major: components[0], minor: components[1], patch: components[2], }) } } impl TryFrom for Version { type Error = zvariant::Error; fn try_from(value: zvariant::OwnedValue) -> Result { zvariant::Value::from(value).try_into() } } #[proxy( default_service = "org.storage.stratis3", default_path = "/org/storage/stratis3", interface = "org.storage.stratis3.Manager.r0" )] trait ManagerV0 { #[zbus(property)] fn version(&self) -> zbus::Result; } const MIN_VERSION: Version = Version { major: 3, minor: 8, patch: 0, }; pub async fn check_stratisd_version(conn: &zbus::Connection) -> zbus::Result<()> { let version = ManagerV0Proxy::new(conn).await?.version().await?; info!(?version, "remote stratisd version"); if version < MIN_VERSION { return Err(zbus::Error::Failure(format!( "expected minimum stratisd of {MIN_VERSION}, got {version}" ))); } Ok(()) } #[derive(Debug, Serialize, Deserialize, zvariant::Type)] pub struct FilesystemSpec { pub name: String, pub size: OptionalByPair, pub size_limit: OptionalByPair, } #[derive(Debug, Deserialize, zvariant::Type)] #[allow(dead_code)] pub struct CreatedFilesystem { pub path: zvariant::OwnedObjectPath, pub name: String, } #[derive(Debug, Deserialize, zvariant::Type)] #[zvariant(signature = "s")] #[serde(transparent)] #[allow(dead_code)] pub struct DestroyedFilesystem { pub uuid: String, } #[serde_as] #[derive(Debug, Deserialize, zvariant::Type, Clone)] #[serde(transparent)] #[zvariant(signature = "s")] pub struct PoolMetadataJson(#[serde_as(as = "JsonString")] PoolMetadata); impl PoolMetadataJson { pub fn into_inner(self) -> PoolMetadata { self.0 } } #[proxy( default_service = "org.storage.stratis3", interface = "org.storage.stratis3.pool.r8" )] pub trait Pool { fn create_filesystems( &self, specs: &[FilesystemSpec], ) -> zbus::Result>>>; fn destroy_filesystems( &self, specs: &[zvariant::ObjectPath<'_>], ) -> zbus::Result>>>; fn snapshot_filesystem( &self, origin: zvariant::ObjectPath<'_>, snapshot_name: &str, ) -> zbus::Result>>; fn metadata(&self, current: bool) -> zbus::Result>; #[zbus(property)] fn name(&self) -> zbus::Result; #[zbus(property)] fn total_physical_size(&self) -> zbus::Result; #[zbus(property)] fn total_physical_used(&self) -> zbus::Result>; } #[proxy( default_service = "org.storage.stratis3", interface = "org.storage.stratis3.filesystem.r8" )] pub trait Filesystem { #[zbus(property)] fn name(&self) -> zbus::Result; #[zbus(property)] fn size(&self) -> zbus::Result; #[zbus(property)] fn size_limit(&self) -> zbus::Result>; #[zbus(property)] fn set_size_limit(&self, value: OptionalByPair) -> zbus::Result<()>; #[zbus(property)] fn used(&self) -> zbus::Result>; #[zbus(property)] fn devnode(&self) -> zbus::Result; }