diff --git a/realmsd/Cargo.toml b/realmsd/Cargo.toml index 376c862..7a13dbe 100644 --- a/realmsd/Cargo.toml +++ b/realmsd/Cargo.toml @@ -6,5 +6,7 @@ edition = "2018" [dependencies] libcitadel = { path = "../libcitadel" } -dbus = "0.8" +zbus = "^2.0.0-beta.5" +zvariant = "2.7.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/realmsd/src/dbus.rs b/realmsd/src/dbus.rs deleted file mode 100644 index ffeca00..0000000 --- a/realmsd/src/dbus.rs +++ /dev/null @@ -1,496 +0,0 @@ -use std::fmt; -use std::sync::Arc; -use std::{result, thread}; - -use dbus::tree::{self, Factory, MTFn, MethodResult, Tree, MethodErr}; -use dbus::blocking::LocalConnection; -use dbus::Message; -use libcitadel::{Result, RealmManager, Realm, RealmEvent, OverlayType, RealmFS, terminal}; -use std::time::Duration; - -type MethodInfo<'a> = tree::MethodInfo<'a, MTFn, TData>; - -// XXX -const UPDATE_TOOL_PATH: &str = "/realms/Shared/citadel-realmfs"; -const SUDO_PATH: &str = "/usr/bin/sudo"; - -const STATUS_REALM_RUNNING: u8 = 1; -const STATUS_REALM_CURRENT: u8 = 2; -const STATUS_REALM_SYSTEM_REALM: u8 = 4; - -const OBJECT_PATH: &str = "/com/subgraph/realms"; -const INTERFACE_NAME: &str = "com.subgraph.realms.Manager"; -const BUS_NAME: &str = "com.subgraph.realms"; - -pub struct DbusServer { - connection: Arc, - manager: Arc, - events: EventHandler, -} - -impl DbusServer { - - pub fn connect(manager: Arc) -> Result { - let connection = LocalConnection::new_system() - .map_err(|e| format_err!("Failed to connect to DBUS system bus: {}", e))?; - let connection = Arc::new(connection); - 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(sssy)"))) - - .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("Restart", (), Self::do_restart) - .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"))) - - .add_m(f.method("RealmFromCitadelPid", (), Self::do_pid_to_realm) - .in_arg(("pid", "u")) - .out_arg(("realm", "s"))) - - .add_m(f.method("RealmConfig", (), Self::do_get_realm_config) - .in_arg(("name", "s")) - .out_arg(("config", "a(ss)"))) - - .add_m(f.method("ListRealmFS", (), Self::do_list_realmfs) - .out_arg(("realmfs", "as"))) - - .add_m(f.method("UpdateRealmFS", (), Self::do_update) - .in_arg(("name", "s"))) - - // 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.append1(realm.name()), - None => ret.append1(""), - }; - 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_restart(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); - } else if let Err(e) = data.manager().start_realm(&realm) { - warn!("failed to restart 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_update(m: &MethodInfo) -> MethodResult { - let name = m.msg.read1()?; - let data = m.tree.get_data().clone(); - let realmfs = data.realmfs_by_name(name)?; - - let command = format!("{} {} update {}", SUDO_PATH, UPDATE_TOOL_PATH, realmfs.name()); - terminal::spawn_citadel_gnome_terminal(Some(command)); - - 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()]) - } - - fn do_pid_to_realm(m: &MethodInfo) -> MethodResult { - let pid = m.msg.read1::()?; - let manager = m.tree.get_data().manager(); - let ret = m.msg.method_return(); - let msg = match manager.realm_by_pid(pid) { - Some(realm) => ret.append1(realm.name()), - None => ret.append1(""), - }; - Ok(vec![msg]) - } - - fn do_get_realm_config(m: &MethodInfo) -> MethodResult { - let name = m.msg.read1()?; - let data = m.tree.get_data().clone(); - let config = data.realm_config(name)?; - Ok(vec![m.msg.method_return().append1(config)]) - } - - fn do_list_realmfs(m: &MethodInfo) -> MethodResult { - let list = m.tree.get_data().realmfs_list(); - Ok(vec![m.msg.method_return().append1(list)]) - } - - pub fn start(&self) -> Result<()> { - let tree = self.build_tree(); - if let Err(err) = self.connection.request_name(BUS_NAME, false, true, false) { - bail!("failed to register DBUS name {}: {}", BUS_NAME, err); - } - - tree.start_receive(self.connection.as_ref()); - - 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 { - self.connection - .process(Duration::from_millis(1000)) - .map_err(context!("Error handling dbus messages"))?; - } - } - - fn send_service_started(&self) { - let signal = Self::create_signal("ServiceStarted"); - if self.connection.channel().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::strings::Interface::new(INTERFACE_NAME).unwrap(); - let member = dbus::strings::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<()> { - if let Err(()) = self.0.channel().send(msg) { - bail!("failed to send DBUS 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::strings::Interface::new(INTERFACE_NAME).unwrap(); - let member = dbus::strings::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 realmfs_by_name(&self, name: &str) -> result::Result { - if let Some(realmfs) = self.manager.realmfs_by_name(name) { - Ok(realmfs) - } else { - result::Result::Err(MethodErr::failed(&format!("Cannot find realmfs {}", name))) - } - } - - fn append_config_flag(list: &mut Vec<(String,String)>, val: bool, name: &str) { - let valstr = if val { "true".to_string() } else { "false".to_string() }; - list.push((name.to_string(), valstr)); - } - - fn realm_config(&self, name: &str) -> result::Result, MethodErr> { - let realm = self.realm_by_name(name)?; - let config = realm.config(); - let mut list = Vec::new(); - Self::append_config_flag(&mut list, config.gpu(), "use-gpu"); - Self::append_config_flag(&mut list, config.wayland(), "use-wayland"); - Self::append_config_flag(&mut list, config.x11(), "use-x11"); - Self::append_config_flag(&mut list, config.sound(), "use-sound"); - Self::append_config_flag(&mut list, config.shared_dir(), "use-shared-dir"); - Self::append_config_flag(&mut list, config.network(), "use-network"); - Self::append_config_flag(&mut list, config.kvm(), "use-kvm"); - Self::append_config_flag(&mut list, config.ephemeral_home(), "use-ephemeral-home"); - let overlay = match config.overlay() { - OverlayType::None => "none", - OverlayType::TmpFS => "tmpfs", - OverlayType::Storage => "storage", - }; - let scheme = match config.terminal_scheme() { - Some(name) => name.to_string(), - None => String::new(), - }; - - list.push(("realmfs".to_string(), config.realmfs().to_string())); - list.push(("overlay".to_string(), overlay.to_string())); - list.push(("terminal-scheme".to_string(), scheme)); - - Ok(list) - } - - fn realm_element(realm: &Realm) -> (String, String, String, u8) { - let name = realm.name().to_owned(); - let desc = Self::realm_description(realm); - let realmfs = realm.config().realmfs().to_owned(); - let status = Self::realm_status(realm); - (name, desc, realmfs, status) - } - - fn realm_list(&self) -> Vec<(String, String, String, u8)> { - self.manager.realm_list() - .iter() - .map(Self::realm_element) - .collect() - } - - fn realm_description(realm: &Realm) -> String { - match realm.notes() { - Some(s) => s, - None => String::new(), - } - } - - fn realm_status(realm: &Realm) -> u8 { - let mut status = 0; - if realm.is_active() { - status |= STATUS_REALM_RUNNING; - } - if realm.is_current() { - status |= STATUS_REALM_CURRENT; - } - if realm.is_system() { - status |= STATUS_REALM_SYSTEM_REALM; - } - status - } - - fn realmfs_list(&self) -> Vec { - self.manager.realmfs_list() - .into_iter() - .map(|fs| fs.name().to_owned()) - .collect() - } -} -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 deleted file mode 100644 index e69de29..0000000 diff --git a/realmsd/src/events.rs b/realmsd/src/events.rs new file mode 100644 index 0000000..7083fb1 --- /dev/null +++ b/realmsd/src/events.rs @@ -0,0 +1,69 @@ +use zbus::{Connection, ObjectServer}; +use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH, realm_status}; +use libcitadel::{RealmEvent, Realm}; + +pub struct EventHandler { + connection: Connection, + realms_server: RealmsManagerServer, +} + +impl EventHandler { + pub fn new(connection: Connection, realms_server: RealmsManagerServer) -> Self { + EventHandler { connection, realms_server } + } + + pub fn handle_event(&self, ev: &RealmEvent) { + if let Err(err) = self.dispatch_event(ev) { + warn!("Error emitting signal for realm event {}: {}", ev, err); + } + } + + fn dispatch_event(&self, ev: &RealmEvent) -> zbus::Result<()> { + 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 with_server(&self, func: F) -> zbus::Result<()> + where + F: Fn(&RealmsManagerServer) -> zbus::Result<()>, + { + let mut object_server = ObjectServer::new(&self.connection); + object_server.at(REALMS_SERVER_OBJECT_PATH, self.realms_server.clone())?; + object_server.with(REALMS_SERVER_OBJECT_PATH, |iface: &RealmsManagerServer| func(iface)) + } + + fn on_started(&self, realm: &Realm) -> zbus::Result<()> { + let namespace = realm.pid_namespace().unwrap_or(String::new()); + let status = realm_status(realm); + self.with_server(|server| server.realm_started(realm.name(), namespace.as_str(), status)) + } + + fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> { + let status = realm_status(realm); + self.with_server(|server| server.realm_stopped(realm.name(), status)) + } + + fn on_new(&self, realm: &Realm) -> zbus::Result<()> { + let status = realm_status(realm); + let description = realm.notes().unwrap_or(String::new()); + self.with_server(|server| server.realm_new(realm.name(), &description, status)) + } + + fn on_removed(&self, realm: &Realm) -> zbus::Result<()> { + self.with_server(|server| server.realm_removed(realm.name())) + } + + fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> { + self.with_server(|server| { + match realm { + Some(realm) => server.realm_current(realm.name(), realm_status(realm)), + None => server.realm_current("", 0), + } + }) + } +} diff --git a/realmsd/src/main.rs b/realmsd/src/main.rs index ac65759..5f81946 100644 --- a/realmsd/src/main.rs +++ b/realmsd/src/main.rs @@ -1,19 +1,39 @@ #[macro_use] extern crate libcitadel; -use libcitadel::{RealmManager, Result, Logger, LogLevel}; -mod dbus; -mod devices; +use zbus::{Connection, fdo}; + +use libcitadel::{Logger, LogLevel, Result}; + +use crate::realms_manager::RealmsManagerServer; + +mod realms_manager; +mod events; + fn main() { - if let Err(e) = run_dbus_server() { + if let Err(e) = run_realm_manager() { warn!("Error: {}", e); } } -fn run_dbus_server() -> Result<()> { - Logger::set_log_level(LogLevel::Verbose); - let manager = RealmManager::load()?; - let server = dbus::DbusServer::connect(manager)?; - server.start()?; - Ok(()) +fn create_system_connection() -> zbus::Result { + let connection = zbus::Connection::new_system()?; + fdo::DBusProxy::new(&connection)?.request_name("com.subgraph.realms", fdo::RequestNameFlags::AllowReplacement.into())?; + Ok(connection) +} + +fn run_realm_manager() -> Result<()> { + Logger::set_log_level(LogLevel::Verbose); + + let connection = create_system_connection() + .map_err(context!("ZBus Connection error"))?; + + let mut object_server = RealmsManagerServer::register(&connection)?; + + loop { + if let Err(err) = object_server.try_handle_next() { + warn!("Error handling DBus message: {}", err); + } + } + } diff --git a/realmsd/src/realms_manager.rs b/realmsd/src/realms_manager.rs new file mode 100644 index 0000000..3d72877 --- /dev/null +++ b/realmsd/src/realms_manager.rs @@ -0,0 +1,376 @@ +use libcitadel::{RealmManager, Realm, OverlayType, Result}; +use std::sync::Arc; +use zbus::{dbus_interface, ObjectServer,Connection}; +use zvariant::derive::Type; +use std::thread; +use std::collections::HashMap; +use serde::{Serialize,Deserialize}; +use crate::events::EventHandler; +use libcitadel::terminal::Base16Scheme; + +pub const REALMS_SERVER_OBJECT_PATH: &str = "/com/subgraph/realms"; + +#[derive(Clone)] +pub struct RealmsManagerServer { + manager: Arc, +} + +const BOOL_CONFIG_VARS: &[&str] = &[ + "use-gpu", "use-wayland", "use-x11", "use-sound", + "use-shared-dir", "use-network", "use-kvm", "use-ephemeral-home" +]; + +fn is_bool_config_variable(variable: &str) -> bool { + BOOL_CONFIG_VARS.iter().any(|&s| s == variable) +} + +fn save_config(realm: &Realm) { + let path = realm.base_path_file("config"); + if let Err(e) = realm.config().write_to(&path) { + warn!("Error writing config file {}: {}", path.display(), e); + } +} + +fn configure_realm_boolean_config(realm: &Realm, variable: &str, value: &str) { + + let val = match value { + "true" => true, + "false" => false, + _ => { + warn!("Not a valid boolean value '{}'", value); + return; + }, + }; + + realm.with_mut_config(|c| { + match variable { + "use-gpu" if c.gpu() != val => c.use_gpu = Some(val), + "use-wayland" if c.wayland() != val => c.use_wayland = Some(val), + "use-x11" if c.x11() != val => c.use_x11 = Some(val), + "use-sound" if c.sound() != val => c.use_sound = Some(val), + "use-shared-dir" if c.shared_dir() != val => c.use_shared_dir = Some(val), + "use-network" if c.network() != val => c.use_network = Some(val), + "use-kvm" if c.kvm() != val => c.use_kvm = Some(val), + "use-ephemeral-home" if c.ephemeral_home() != val => c.use_ephemeral_home = Some(val), + _ => {}, + } + }); + save_config(realm); +} + +fn configure_realm(realm: &Realm, variable: &str, value: &str) { + if is_bool_config_variable(variable) { + configure_realm_boolean_config(realm, variable, value); + } else if variable == "overlay" { + if value == "tmpfs" || value == "storage" || value == "none" { + realm.with_mut_config(|c| { + c.overlay = Some(value.to_string()); + }); + save_config(realm); + } else { + warn!("Invalid storage type '{}'", value); + return; + } + } else if variable == "terminal-scheme" { + if Base16Scheme::by_name(value).is_none() { + warn!("No terminal color scheme with name '{}' available", value); + } + realm.with_mut_config(|c| { + c.terminal_scheme = Some(value.to_string()); + }); + save_config(realm); + } else if variable == "realmfs" { + warn!("Changing realmfs config variable not implemented"); + } else { + warn!("Unknown config variable '{}'", variable); + } +} + +impl RealmsManagerServer { + + fn register_events(&self, connection: &Connection) -> Result<()> { + let events = EventHandler::new(connection.clone(), self.clone()); + self.manager.add_event_handler(move |ev| events.handle_event(ev)); + self.manager.start_event_task() + } + + pub fn register(connection: &Connection) -> Result { + let manager = RealmManager::load()?; + let iface = RealmsManagerServer { manager }; + iface.register_events(connection)?; + let mut object_server = ObjectServer::new(connection); + object_server.at(REALMS_SERVER_OBJECT_PATH, iface).map_err(context!("ZBus error"))?; + Ok(object_server) + } + +} + + +#[dbus_interface(name = "com.subgraph.realms.Manager")] +impl RealmsManagerServer { + + fn set_current(&self, name: &str) { + if let Some(realm) = self.manager.realm_by_name(name) { + if let Err(err) = self.manager.set_current_realm(&realm) { + warn!("set_current_realm({}) failed: {}", name, err); + } + } + } + + fn get_current(&self) -> String { + match self.manager.current_realm() { + Some(realm) => realm.name().to_string(), + None => String::new(), + } + } + + fn list(&self) -> Vec { + let mut realms = Vec::new(); + for r in self.manager.realm_list() { + realms.push(RealmItem::new_from_realm(&r)); + } + realms + } + + fn start(&self, name: &str) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return, + }; + let manager = self.manager.clone(); + + thread::spawn(move || { + if let Err(e) = manager.start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), e); + } + }); + } + + fn stop(&self, name: &str) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return, + }; + let manager = self.manager.clone(); + + thread::spawn(move || { + if let Err(e) = manager.stop_realm(&realm) { + warn!("failed to stop realm {}: {}", realm.name(), e); + } + }); + } + + fn restart(&self, name: &str) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return, + }; + let manager = self.manager.clone(); + + thread::spawn(move || { + if let Err(e) = manager.stop_realm(&realm) { + warn!("failed to stop realm {}: {}", realm.name(), e); + } else if let Err(e) = manager.start_realm(&realm) { + warn!("failed to restart realm {}: {}", realm.name(), e); + } + }); + } + + fn terminal(&self, name: &str) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return, + }; + let manager = self.manager.clone(); + + thread::spawn(move || { + if !realm.is_active() { + if let Err(err) = manager.start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), err); + return; + } + } + if let Err(err) = manager.launch_terminal(&realm) { + warn!("error launching terminal for realm {}: {}", realm.name(), err); + } + }); + } + + fn run(&self, name: &str, args: Vec) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return, + }; + let manager = self.manager.clone(); + + thread::spawn(move || { + if !realm.is_active() { + if let Err(err) = manager.start_realm(&realm) { + warn!("failed to start realm {}: {}", realm.name(), err); + return; + } + } + if let Err(err) = manager.run_in_realm(&realm, &args, true) { + warn!("error running {:?} in realm {}: {}", args, realm.name(), err); + } + }); + } + + fn realm_from_citadel_pid(&self, pid: u32) -> String { + match self.manager.realm_by_pid(pid) { + Some(r) => r.name().to_string(), + None => String::new(), + } + } + + fn realm_config(&self, name: &str) -> RealmConfig { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => return RealmConfig::new(), + }; + RealmConfig::new_from_realm(&realm) + } + + fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) { + let realm = match self.manager.realm_by_name(name) { + Some(r) => r, + None => { + warn!("No realm named '{}' found in RealmSetConfig", name); + return; + }, + }; + + for var in &vars { + configure_realm(&realm, &var.0, &var.1); + } + } + + fn realm_exists(&self, name: &str) -> bool { + Realm::is_valid_name(name) && self.manager.realm_by_name(name).is_some() + } + + fn create_realm(&self, name: &str) -> bool { + if let Err(err) = self.manager.new_realm(name) { + warn!("Error creating realm ({}): {}", name, err); + false + } else { + true + } + } + + fn list_realm_f_s(&self) -> Vec { + self.manager.realmfs_list() + .into_iter() + .map(|fs| fs.name().to_owned()) + .collect() + } + + fn update_realm_f_s(&self, _name: &str) { + + } + + #[dbus_interface(signal)] + pub fn realm_started(&self, realm: &str, namespace: &str, status: u8) -> zbus::Result<()> { Ok(()) } + + #[dbus_interface(signal)] + pub fn realm_stopped(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) } + + #[dbus_interface(signal)] + pub fn realm_new(&self, realm: &str, description: &str, status: u8) -> zbus::Result<()> { Ok(()) } + + #[dbus_interface(signal)] + pub fn realm_removed(&self, realm: &str) -> zbus::Result<()> { Ok(()) } + + #[dbus_interface(signal)] + pub fn realm_current(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) } + + #[dbus_interface(signal)] + pub fn service_started(&self) -> zbus::Result<()> { Ok(()) } + +} + +const STATUS_REALM_RUNNING: u8 = 1; +const STATUS_REALM_CURRENT: u8 = 2; +const STATUS_REALM_SYSTEM_REALM: u8 = 4; + +pub fn realm_status(realm: &Realm) -> u8 { + let mut status = 0; + if realm.is_active() { + status |= STATUS_REALM_RUNNING; + } + if realm.is_current() { + status |= STATUS_REALM_CURRENT; + } + if realm.is_system() { + status |= STATUS_REALM_SYSTEM_REALM; + } + status +} + +#[derive(Deserialize,Serialize,Type)] +struct RealmItem { + name: String, + description: String, + realmfs: String, + namespace: String, + status: u8, +} + +impl RealmItem { + fn new_from_realm(realm: &Realm) -> Self { + let name = realm.name().to_string(); + let description = realm.notes().unwrap_or(String::new()); + let realmfs = realm.config().realmfs().to_string(); + let namespace = realm.pid_namespace().unwrap_or(String::new()); + let status = realm_status(realm); + RealmItem { name, description, realmfs, namespace, status } + } +} + +#[derive(Deserialize,Serialize,Type)] +struct RealmConfig { + items: HashMap, +} + +impl RealmConfig { + fn new() -> Self { + RealmConfig { items: HashMap::new() } + } + + fn new_from_realm(realm: &Realm) -> Self { + let mut this = RealmConfig { items: HashMap::new() }; + let config = realm.config(); + this.add_bool("use-gpu", config.gpu()); + this.add_bool("use-wayland", config.wayland()); + this.add_bool("use-x11", config.x11()); + this.add_bool("use-sound", config.sound()); + this.add_bool("use-shared-dir", config.shared_dir()); + this.add_bool("use-network", config.network()); + this.add_bool("use-kvm", config.kvm()); + this.add_bool("use-ephemeral-home", config.ephemeral_home()); + + let overlay = match config.overlay() { + OverlayType::None => "none", + OverlayType::TmpFS => "tmpfs", + OverlayType::Storage => "storage", + }; + this.add("overlay", overlay); + + let scheme = match config.terminal_scheme() { + Some(name) => name.to_string(), + None => String::new(), + }; + this.add("terminal-scheme", scheme); + this.add("realmfs", config.realmfs()); + this + } + + fn add_bool(&mut self, name: &str, val: bool) { + let valstr = if val { "true".to_string() } else { "false".to_string() }; + self.add(name, valstr); + } + + fn add(&mut self, k: S, v: T) where S: Into, T: Into { + self.items.insert(k.into(), v.into()); + } +}