From d0eada1e3bd890c787bec153804228254570a69b Mon Sep 17 00:00:00 2001 From: Bruce Leidl Date: Sun, 25 Aug 2019 17:15:13 -0400 Subject: [PATCH] Initial commit of realms dbus daemon --- Cargo.lock | 9 + Cargo.toml | 2 +- libcitadel/src/realm/manager.rs | 6 + realmsd/Cargo.toml | 11 + realmsd/src/dbus.rs | 369 ++++++++++++++++++++++++++++++++ realmsd/src/devices.rs | 0 realmsd/src/main.rs | 18 ++ 7 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 realmsd/Cargo.toml create mode 100644 realmsd/src/dbus.rs create mode 100644 realmsd/src/devices.rs create mode 100644 realmsd/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index daece00..a81ab3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -562,6 +562,15 @@ dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "realmsd" +version = "0.1.0" +dependencies = [ + "dbus 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libcitadel 0.1.0", +] + [[package]] name = "redox_syscall" version = "0.1.51" diff --git a/Cargo.toml b/Cargo.toml index f4582d7..5794dae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["citadel-realms", "citadel-tool" ] +members = ["citadel-realms", "citadel-tool", "realmsd" ] [profile.release] lto = true codegen-units = 1 diff --git a/libcitadel/src/realm/manager.rs b/libcitadel/src/realm/manager.rs index 002381c..3805935 100644 --- a/libcitadel/src/realm/manager.rs +++ b/libcitadel/src/realm/manager.rs @@ -198,6 +198,7 @@ impl RealmManager { pub fn start_realm(&self, realm: &Realm) -> Result<()> { if realm.is_active() { info!("ignoring start request on already running realm '{}'", realm.name()); + return Ok(()); } info!("Starting realm {}", realm.name()); self._start_realm(realm, &mut HashSet::new())?; @@ -266,6 +267,7 @@ impl RealmManager { pub fn stop_realm(&self, realm: &Realm) -> Result<()> { if !realm.is_active() { info!("ignoring stop request on realm '{}' which is not running", realm.name()); + return Ok(()); } info!("Stopping realm {}", realm.name()); @@ -302,6 +304,10 @@ impl RealmManager { Some(realm) } + pub fn current_realm(&self) -> Option { + self.inner_mut().realms.current() + } + fn choose_some_current_realm(&self) { if let Err(e) = self.inner_mut().realms.choose_some_current() { warn!("error choosing new current realm: {}", e); diff --git a/realmsd/Cargo.toml b/realmsd/Cargo.toml new file mode 100644 index 0000000..2cbc620 --- /dev/null +++ b/realmsd/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "realmsd" +version = "0.1.0" +authors = ["Bruce Leidl "] +edition = "2018" + +[dependencies] +libcitadel = { path = "../libcitadel" } +failure = "0.1" +dbus = "0.6.4" + diff --git a/realmsd/src/dbus.rs b/realmsd/src/dbus.rs new file mode 100644 index 0000000..386f505 --- /dev/null +++ b/realmsd/src/dbus.rs @@ -0,0 +1,369 @@ +use std::sync::Arc; +use std::collections::HashMap; +use std::{result, thread}; + +use dbus::tree::{self, Factory, MTFn, MethodResult, Tree, MethodErr}; +use dbus::{Connection, NameFlag, Message}; +use libcitadel::{Result, RealmManager, Realm, RealmEvent}; +use std::fmt; + +type MethodInfo<'a> = tree::MethodInfo<'a, MTFn, TData>; + +const STATUS_REALM_NOT_RUNNING: u8 = 0; +const STATUS_REALM_RUNNING_NOT_CURRENT: u8 = 1; +const STATUS_REALM_RUNNING_CURRENT: u8 = 2; + +const OBJECT_PATH: &str = "/com/subgraph/realms"; +const INTERFACE_NAME: &str = "com.subgraph.realms.Manager"; +const BUS_NAME: &str = "com.subgraph.realms"; + +const OBJECT_MANAGER_INTERFACE: &str = "org.freedesktop.DBus.ObjectManager"; +const VPN_CONNECTION_INTERFACE: &str = "org.freedesktop.VPN.Connection"; + +pub struct DbusServer { + connection: Arc, + manager: Arc, + events: EventHandler, +} + +impl DbusServer { + + pub fn connect(manager: Arc) -> Result { + let connection = Arc::new(Connection::get_private(dbus::BusType::System)?); + let events = EventHandler::new(connection.clone()); + let server = DbusServer { events, connection, manager }; + Ok(server) + } + + fn build_tree(&self) -> Tree, TData> { + let f = Factory::new_fn::(); + let data = TreeData::new(self.manager.clone()); + let interface = f.interface(INTERFACE_NAME, ()) + // Methods + .add_m(f.method("SetCurrent", (), Self::do_set_current) + .in_arg(("name", "s"))) + + .add_m(f.method("GetCurrent", (), Self::do_get_current) + .out_arg(("name", "s"))) + + .add_m(f.method("List", (), Self::do_list) + .out_arg(("realms", "a{sy}"))) + + .add_m(f.method("Start", (), Self::do_start) + .in_arg(("name", "s"))) + + .add_m(f.method("Stop", (), Self::do_stop) + .in_arg(("name", "s"))) + + .add_m(f.method("Terminal", (), Self::do_terminal) + .in_arg(("name", "s"))) + + .add_m(f.method("Run", (), Self::do_run) + .in_arg(("name", "s")) + .in_arg(("args", "as"))) + + // Signals + .add_s(f.signal("RealmStarted", ()) + .arg(("realm", "s"))) + .add_s(f.signal("RealmStopped", ()) + .arg(("realm", "s"))) + .add_s(f.signal("RealmNew", ()) + .arg(("realm", "s"))) + .add_s(f.signal("RealmRemoved", ()) + .arg(("realm","s"))) + .add_s(f.signal("RealmCurrent", ()) + .arg(("realm", "s"))) + .add_s(f.signal("ServiceStarted", ())); + + let obpath = f.object_path(OBJECT_PATH, ()) + .introspectable() + .add(interface); + + f.tree(data).add(obpath) + } + + fn do_list(m: &MethodInfo) -> MethodResult { + let list = m.tree.get_data().realm_list(); + Ok(vec![m.msg.method_return().append1(list)]) + } + + fn do_set_current(m: &MethodInfo) -> MethodResult { + let manager = m.tree.get_data().manager(); + let name = m.msg.read1()?; + if let Some(realm) = manager.realm_by_name(name) { + if let Err(err) = manager.set_current_realm(&realm) { + warn!("set_current_realm({}) failed: {}", name, err); + } + } + Ok(vec![m.msg.method_return()]) + } + + fn do_get_current(m: &MethodInfo) -> MethodResult { + let manager = m.tree.get_data().manager(); + let ret = m.msg.method_return(); + let msg = match manager.current_realm() { + Some(realm) => ret.append(realm.name()), + None => ret.append(""), + }; + Ok(vec![msg]) + } + + fn do_start(m: &MethodInfo) -> MethodResult { + let name = m.msg.read1()?; + let data = m.tree.get_data().clone(); + let realm = data.realm_by_name(name)?; + thread::spawn(move || { + if let Err(e) = data.manager().start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), e); + } + }); + Ok(vec![m.msg.method_return()]) + } + + fn do_stop(m: &MethodInfo) -> MethodResult { + let name = m.msg.read1()?; + let data = m.tree.get_data().clone(); + let realm = data.realm_by_name(name)?; + thread::spawn(move || { + if let Err(e) = data.manager().stop_realm(&realm) { + warn!("failed to stop realm {}: {}", realm.name(), e); + } + }); + Ok(vec![m.msg.method_return()]) + } + + fn do_terminal(m: &MethodInfo) -> MethodResult { + let name = m.msg.read1()?; + let data = m.tree.get_data().clone(); + let realm = data.realm_by_name(name)?; + thread::spawn(move || { + if !realm.is_active() { + if let Err(err) = data.manager().start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), err); + return; + } + } + if let Err(err) = data.manager().launch_terminal(&realm) { + warn!("error launching terminal for realm {}: {}", realm.name(), err); + } + }); + Ok(vec![m.msg.method_return()]) + } + + fn do_run(m: &MethodInfo) -> MethodResult { + let (name,args) = m.msg.read2::<&str, Vec>()?; + let data = m.tree.get_data().clone(); + let realm = data.realm_by_name(name)?; + thread::spawn(move || { + if !realm.is_active() { + if let Err(err) = data.manager().start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), err); + return; + } + } + if let Err(err) = data.manager().run_in_realm(&realm, &args, true) { + warn!("error running {:?} in realm {}: {}", args, realm.name(), err); + } + }); + Ok(vec![m.msg.method_return()]) + } + + pub fn start(&self) -> Result<()> { + let tree = self.build_tree(); + self.connection.register_name(BUS_NAME, NameFlag::ReplaceExisting as u32)?; + tree.set_registered(&self.connection, true)?; + self.connection.add_handler(tree); + + self.receive_signals_from(VPN_CONNECTION_INTERFACE)?; + self.receive_signals_from(OBJECT_MANAGER_INTERFACE)?; + + self.manager.add_event_handler({ + let events = self.events.clone(); + move |ev| events.handle_event(ev) + }); + + if let Err(e) = self.manager.start_event_task() { + warn!("error starting realm manager event task: {}", e); + } + + self.send_service_started(); + + loop { + if let Some(msg) = self.connection.incoming(1000).next() { + self.process_message(msg)?; + } + } + } + + fn process_message(&self, _msg: Message) -> Result<()> { + // add handlers for expected signals here + Ok(()) + } + + fn receive_signals_from(&self, interface: &str) -> Result<()> { + let rule = format!("type=signal,interface={}", interface); + self.connection.add_match(rule.as_str())?; + Ok(()) + } + + fn send_service_started(&self) { + let signal = Self::create_signal("ServiceStarted"); + if self.connection.send(signal).is_err() { + warn!("Failed to send ServiceStarted signal"); + } + } + + fn create_signal(name: &str) -> Message { + let path = dbus::Path::new(OBJECT_PATH).unwrap(); + let iface = dbus::Interface::new(INTERFACE_NAME).unwrap(); + let member = dbus::Member::new(name).unwrap(); + Message::signal(&path, &iface, &member) + } +} + +/// Wraps a connection instance and only expose the send() method. +/// Sending a message does not read or write any of the internal +/// Connection object state other than the native handle for the +/// connection. It should be safe to share this across threads as +/// internally libdbus uses a mutex to control concurrent access +/// to the dbus_connection_send() function. +#[derive(Clone)] +struct ConnectionSender(Arc); + +unsafe impl Send for ConnectionSender {} +unsafe impl Sync for ConnectionSender {} + +impl ConnectionSender { + fn new(connection: Arc) -> Self { + ConnectionSender(connection) + } + + fn send(&self, msg: Message) -> Result<()> { + self.0.send(msg) + .map_err(|()| failure::err_msg("failed to send message"))?; + Ok(()) + } +} + +#[derive(Clone)] +struct EventHandler { + sender: ConnectionSender, +} + +impl EventHandler { + fn new(conn: Arc) -> EventHandler { + EventHandler { + sender: ConnectionSender::new(conn), + } + } + + fn handle_event(&self, ev: &RealmEvent) { + match ev { + RealmEvent::Started(realm) => self.on_started(realm), + RealmEvent::Stopped(realm) => self.on_stopped(realm), + RealmEvent::New(realm) => self.on_new(realm), + RealmEvent::Removed(realm) => self.on_removed(realm), + RealmEvent::Current(realm) => self.on_current(realm.as_ref()), + } + } + + fn on_started(&self, realm: &Realm) { + self.send_realm_signal("RealmStarted", Some(realm)); + } + + fn on_stopped(&self, realm: &Realm) { + self.send_realm_signal("RealmStopped", Some(realm)); + } + + fn on_new(&self, realm: &Realm) { + self.send_realm_signal("RealmNew", Some(realm)); + } + + fn on_removed(&self, realm: &Realm) { + self.send_realm_signal("RealmRemoved", Some(realm)); + } + + fn on_current(&self, realm: Option<&Realm>) { + self.send_realm_signal("RealmCurrent", realm); + } + + fn create_realm_signal(name: &str) -> Message { + let path = dbus::Path::new(OBJECT_PATH).unwrap(); + let iface = dbus::Interface::new(INTERFACE_NAME).unwrap(); + let member = dbus::Member::new(name).unwrap(); + Message::signal(&path, &iface, &member) + } + + fn send_realm_signal(&self, sig_name: &str, realm: Option<&Realm>) { + let realm_name = match realm { + Some(r) => r.name(), + None => "", + }; + + let msg = Self::create_realm_signal(sig_name) + .append1(realm_name); + + if let Err(e) = self.sender.send(msg) { + warn!("Could not send signal '{}': {}", sig_name, e); + } + } +} + +#[derive(Clone)] +struct TreeData { + manager: Arc, +} + +impl TreeData { + fn new(manager: Arc) -> TreeData { + TreeData { + manager, + } + } + + fn manager(&self) -> &RealmManager { + &self.manager + } + + fn realm_by_name(&self, name: &str) -> result::Result { + if let Some(realm) = self.manager.realm_by_name(name) { + Ok(realm) + } else { + result::Result::Err(MethodErr::failed(&format!("Cannot find realm {}", name))) + } + } + + fn realm_list(&self) -> HashMap { + self.manager.realm_list() + .iter() + .map(|r| (r.name().to_owned(), Self::realm_status(r) )) + .collect() + } + + fn realm_status(realm: &Realm) -> u8 { + if realm.is_active() && realm.is_current() { + STATUS_REALM_RUNNING_CURRENT + } else if realm.is_active() { + STATUS_REALM_RUNNING_NOT_CURRENT + } else { + STATUS_REALM_NOT_RUNNING + } + } +} +impl fmt::Debug for TreeData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} + +#[derive(Copy, Clone, Default, Debug)] +struct TData; + +impl tree::DataType for TData { + type Tree = TreeData; + type ObjectPath = (); + type Property = (); + type Interface = (); + type Method = (); + type Signal = (); +} diff --git a/realmsd/src/devices.rs b/realmsd/src/devices.rs new file mode 100644 index 0000000..e69de29 diff --git a/realmsd/src/main.rs b/realmsd/src/main.rs new file mode 100644 index 0000000..e68b58d --- /dev/null +++ b/realmsd/src/main.rs @@ -0,0 +1,18 @@ +#[macro_use] extern crate libcitadel; +use libcitadel::{RealmManager,Result}; + +mod dbus; +mod devices; + +fn main() { + if let Err(e) = run_dbus_server() { + warn!("Error: {}", e); + } +} + +fn run_dbus_server() -> Result<()> { + let manager = RealmManager::load()?; + let server = dbus::DbusServer::connect(manager)?; + server.start()?; + Ok(()) +}