Initial commit of realms dbus daemon

This commit is contained in:
Bruce Leidl 2019-08-25 17:15:13 -04:00
parent 3820bcc08f
commit d0eada1e3b
7 changed files with 414 additions and 1 deletions

9
Cargo.lock generated
View File

@ -562,6 +562,15 @@ dependencies = [
"proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.51" version = "0.1.51"

View File

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["citadel-realms", "citadel-tool" ] members = ["citadel-realms", "citadel-tool", "realmsd" ]
[profile.release] [profile.release]
lto = true lto = true
codegen-units = 1 codegen-units = 1

View File

@ -198,6 +198,7 @@ impl RealmManager {
pub fn start_realm(&self, realm: &Realm) -> Result<()> { pub fn start_realm(&self, realm: &Realm) -> Result<()> {
if realm.is_active() { if realm.is_active() {
info!("ignoring start request on already running realm '{}'", realm.name()); info!("ignoring start request on already running realm '{}'", realm.name());
return Ok(());
} }
info!("Starting realm {}", realm.name()); info!("Starting realm {}", realm.name());
self._start_realm(realm, &mut HashSet::new())?; self._start_realm(realm, &mut HashSet::new())?;
@ -266,6 +267,7 @@ impl RealmManager {
pub fn stop_realm(&self, realm: &Realm) -> Result<()> { pub fn stop_realm(&self, realm: &Realm) -> Result<()> {
if !realm.is_active() { if !realm.is_active() {
info!("ignoring stop request on realm '{}' which is not running", realm.name()); info!("ignoring stop request on realm '{}' which is not running", realm.name());
return Ok(());
} }
info!("Stopping realm {}", realm.name()); info!("Stopping realm {}", realm.name());
@ -302,6 +304,10 @@ impl RealmManager {
Some(realm) Some(realm)
} }
pub fn current_realm(&self) -> Option<Realm> {
self.inner_mut().realms.current()
}
fn choose_some_current_realm(&self) { fn choose_some_current_realm(&self) {
if let Err(e) = self.inner_mut().realms.choose_some_current() { if let Err(e) = self.inner_mut().realms.choose_some_current() {
warn!("error choosing new current realm: {}", e); warn!("error choosing new current realm: {}", e);

11
realmsd/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "realmsd"
version = "0.1.0"
authors = ["Bruce Leidl <bruce@subgraph.com>"]
edition = "2018"
[dependencies]
libcitadel = { path = "../libcitadel" }
failure = "0.1"
dbus = "0.6.4"

369
realmsd/src/dbus.rs Normal file
View File

@ -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>, 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<Connection>,
manager: Arc<RealmManager>,
events: EventHandler,
}
impl DbusServer {
pub fn connect(manager: Arc<RealmManager>) -> Result<DbusServer> {
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<MTFn<TData>, TData> {
let f = Factory::new_fn::<TData>();
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<String>>()?;
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<Connection>);
unsafe impl Send for ConnectionSender {}
unsafe impl Sync for ConnectionSender {}
impl ConnectionSender {
fn new(connection: Arc<Connection>) -> 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<Connection>) -> 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<RealmManager>,
}
impl TreeData {
fn new(manager: Arc<RealmManager>) -> TreeData {
TreeData {
manager,
}
}
fn manager(&self) -> &RealmManager {
&self.manager
}
fn realm_by_name(&self, name: &str) -> result::Result<Realm, MethodErr> {
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<String, u8> {
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, "<TreeData>")
}
}
#[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 = ();
}

0
realmsd/src/devices.rs Normal file
View File

18
realmsd/src/main.rs Normal file
View File

@ -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(())
}