diff --git a/sfsmcd/src/ifaces/errors.rs b/sfsmcd/src/ifaces/errors.rs new file mode 100644 index 0000000..b613744 --- /dev/null +++ b/sfsmcd/src/ifaces/errors.rs @@ -0,0 +1,16 @@ +#[derive(Debug)] +pub enum ServiceError { + NoPermission, + AlreadyStarted, + AlreadyStopped +} + +impl core::fmt::Display for ServiceError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::NoPermission => write!(f, "no permission"), + Self::AlreadyStarted => write!(f, "already started"), + Self::AlreadyStopped => write!(f, "already stopped") + } + } +} \ No newline at end of file diff --git a/sfsmcd/src/ifaces/mod.rs b/sfsmcd/src/ifaces/mod.rs new file mode 100644 index 0000000..583fb58 --- /dev/null +++ b/sfsmcd/src/ifaces/mod.rs @@ -0,0 +1,116 @@ +use dbus::tree::DataType; +use std::sync::Arc; +use tokio::sync::RwLock; +#[allow(non_camel_case_types)] +pub mod traits; +mod structs; +mod errors; + +pub trait Interface { + fn path() -> dbus::Path<'static>; +} + +#[derive(Clone, Debug, Default)] +pub struct Data { + daemon: Arc> +} + +#[derive(Debug)] +pub struct ObjectPathData { + daemon: Option>> +} + +impl ObjectPathData { + pub fn daemon(&self) -> Option>> { + self.daemon.clone() + } +} + +impl From for ObjectPathData { + fn from(d: structs::DaemonService) -> Self { + Self { + daemon: Some(Arc::new(RwLock::new(d))) + } + } +} + +impl DataType for Data { + type Tree = (); + type ObjectPath = ObjectPathData; + type Property = (); + type Interface = (); + type Method = (); + type Signal = (); +} + +#[macro_export] +macro_rules! handle_iface_generic { + ($m:ident, $no_path:ident, $path:ident, |$data:ident: $data_ty:ty| {$($d:tt)+} $($f:tt)+) => {move |$m| tokio::task::block_in_place(move || { + // Prepare no-path error + let $no_path = dbus::tree::MethodErr::no_path($m.path.get_name()); + let $path = $m.path.get_name(); + let $data = $m.path.get_data(); + // Get data depending on the object path + $($d)+ + // If the object path couldn't be found, the method will return the no-path error + + let f = |$data: $data_ty| $($f)+; + + // Run a Future on the dedicated tokio runtime + iface_rt_handle().block_on(f($data)) + })} +} + +#[macro_export] +#[inline] +macro_rules! handle_org_ddnss_sfs_mc_Service { + ($m:ident, |$data:ident| $($f:tt)+) => { + $crate::handle_iface_generic!($m, no_path, path, |$data: std::sync::Arc>| { + let $data = if path == &DaemonService::path() { + $data.daemon().ok_or(no_path) + } else { + Err(no_path) + }?; + } $($f)+) + }; +} + +#[allow(non_snake_case)] +pub mod Service { + pub use super::{ + traits::org_ddnss_sfs_mc_Service, + structs::DaemonService, + errors::ServiceError + }; + pub use crate::handle_org_ddnss_sfs_mc_Service as handle; + + impl super::Interface for DaemonService { + fn path() -> dbus::Path<'static> { + dbus::Path::new("/").unwrap() + } + } + + impl Default for DaemonService { + fn default() -> Self { + Self + } + } + + impl org_ddnss_sfs_mc_Service for DaemonService { + fn start(&mut self) -> Result, ServiceError> { + Err(ServiceError::AlreadyStarted) + } + + fn is_running(&self) -> bool { + true + } + + fn stop(&mut self) -> Result<(), ServiceError> { + std::process::exit(0); + } + + fn restart(&mut self) -> Result, ServiceError> { + Err(ServiceError::NoPermission) + } + } +} \ No newline at end of file diff --git a/sfsmcd/src/ifaces/structs.rs b/sfsmcd/src/ifaces/structs.rs new file mode 100644 index 0000000..3e0384e --- /dev/null +++ b/sfsmcd/src/ifaces/structs.rs @@ -0,0 +1,2 @@ +#[derive(Debug)] +pub struct DaemonService; \ No newline at end of file diff --git a/sfsmcd/src/ifaces/traits.rs b/sfsmcd/src/ifaces/traits.rs new file mode 100644 index 0000000..433e89a --- /dev/null +++ b/sfsmcd/src/ifaces/traits.rs @@ -0,0 +1,19 @@ +use super::errors::ServiceError; +pub trait org_ddnss_sfs_mc_Service { + + fn start(&mut self) -> Result, ServiceError>; + fn stop(&mut self) -> Result<(), ServiceError>; + fn is_running(&self) -> bool; + fn restart(&mut self) -> Result, ServiceError> { + if self.is_running() { + self.stop()?; + } + self.start() + } +} + +impl core::fmt::Debug for (dyn org_ddnss_sfs_mc_Service + 'static) { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "org.ddnss.sfs.mc.Service") + } +} \ No newline at end of file diff --git a/sfsmcd/src/main.rs b/sfsmcd/src/main.rs index 1f73c6c..083bb2c 100644 --- a/sfsmcd/src/main.rs +++ b/sfsmcd/src/main.rs @@ -1,10 +1,51 @@ use log::{ debug, info, warn, error }; use dbus::{ message::MatchRule, - channel::MatchingReceiver + channel::MatchingReceiver, + tree::Factory, + tree::MethodErr }; +use ifaces::Interface; +use std::sync::Arc; + +mod ifaces; const DBUS_NAME: &'static str = "org.ddnss.sfs.mc"; +// Interface tokio runtime +static mut IFACE_RT: Option = None; +static mut IFACE_RT_HANDLE: Option> = None; +static IFACE_RT_INIT: std::sync::Once = std::sync::Once::new(); + +fn iface_name(iface: &str) -> String { + DBUS_NAME.to_owned() + "." + iface +} + +fn iface_rt_handle() -> Arc { + IFACE_RT_INIT.call_once(|| { + unsafe { + IFACE_RT = Some(tokio::runtime::Builder::new() + .thread_name("iface-runtime-worker") + .build() + .unwrap() + ); + + IFACE_RT_HANDLE = match &IFACE_RT { + Some(iface_rt) => Some(Arc::new(iface_rt + .handle() + .clone() + )), + _ => unreachable!() + }; + } + }); + + unsafe { + match &IFACE_RT_HANDLE { + Some(iface_rt_handle) => iface_rt_handle.clone(), + _ => unreachable!() + } + } +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -17,6 +58,46 @@ async fn main() -> Result<(), Box> { debug!("PID: {}, User: {}", std::process::id(), std::env::var("USER").unwrap_or("N/A".into())); + // Build interfaces + let f = Factory::new_sync::(); + let mut iface_map = std::collections::HashMap::new(); + iface_map.insert(iface_name("Service"), Arc::new({ + use ifaces::Service::*; + + f.interface(iface_name("Service"), ()) + // org_ddnss_sfs_mc_Service::start() + .add_m(f.method_sync("Start", (), handle!(m, |data| async move { + org_ddnss_sfs_mc_Service::start(&mut *data.write().await) + .map(|_| vec![m.msg.method_return()]) + .map_err(|err| MethodErr::failed(&err)) + }))) + // org_ddnss_sfs_mc_Service::stop() + .add_m(f.method_sync("Stop", (), handle!(m, |data| async move { + org_ddnss_sfs_mc_Service::stop(&mut *data.write().await) + .map(|_| vec![m.msg.method_return()]) + .map_err(|err| MethodErr::failed(&err)) + }))) + // org_ddnss_sfs_mc_Service::restart() + .add_m(f.method_sync("Restart", (), handle!(m, |data| async move { + org_ddnss_sfs_mc_Service::restart(&mut *data.write().await) + .map(|_| vec![m.msg.method_return()]) + .map_err(|err| MethodErr::failed(&err)) + }))) + // org_ddnss_sfs_mc_Service::is_running() + .add_m(f.method_sync("IsRunning", (), handle!(m, |data| async move { + Ok(vec![m.msg.method_return().append1( + org_ddnss_sfs_mc_Service::is_running(&*data.read().await) + )]) + })).out_arg("b")) + })); + + // Add interfaces to object paths in a new tree + let tree = f.tree(()) + .add(f.object_path(ifaces::Service::DaemonService::path(), ifaces::Service::DaemonService.into()) + .introspectable() + .add(iface_map[&iface_name("Service")].clone()) + ); + // Connect with D-Bus let (resource, c) = dbus_tokio::connection::new_system_sync()?; debug!("D-Bus unique name: {}", c.unique_name()); @@ -37,7 +118,13 @@ async fn main() -> Result<(), Box> { }; // Receive method calls - c.start_receive(MatchRule::new_method_call(), Box::new(move |msg, conn| { + c.start_receive(MatchRule::new_method_call(), Box::new(move |msg, c| { + use dbus::channel::Sender; + if let Some(replies) = tree.handle(&msg) { + for r in replies { + let _ = c.send(r); + } + } true }));