Initial commit of realms dbus daemon
This commit is contained in:
parent
3820bcc08f
commit
d0eada1e3b
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -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"
|
||||
|
@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["citadel-realms", "citadel-tool" ]
|
||||
members = ["citadel-realms", "citadel-tool", "realmsd" ]
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
@ -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<Realm> {
|
||||
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);
|
||||
|
11
realmsd/Cargo.toml
Normal file
11
realmsd/Cargo.toml
Normal 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
369
realmsd/src/dbus.rs
Normal 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
0
realmsd/src/devices.rs
Normal file
18
realmsd/src/main.rs
Normal file
18
realmsd/src/main.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user