forked from brl/citadel-tools
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)",
|
"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"
|
||||||
|
@ -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
|
||||||
|
@ -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
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