New improved realms daemon implementation

This commit is contained in:
Bruce Leidl 2024-11-13 11:54:40 -05:00
parent 24f786cf75
commit 11b3e8a016
16 changed files with 1446 additions and 313 deletions

677
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,12 @@
<busconfig> <busconfig>
<policy user="root"> <policy user="root">
<allow own="com.subgraph.realms"/> <allow own="com.subgraph.realms"/>
<allow own="com.subgraph.Realms2"/>
</policy> </policy>
<policy context="default"> <policy context="default">
<allow send_destination="com.subgraph.realms"/> <allow send_destination="com.subgraph.realms"/>
<allow send_destination="com.subgraph.Realms2"/>
<allow send_destination="com.subgraph.realms" <allow send_destination="com.subgraph.realms"
send_interface="org.freedesktop.DBus.Properties"/> send_interface="org.freedesktop.DBus.Properties"/>
<allow send_destination="com.subgraph.realms" <allow send_destination="com.subgraph.realms"

View File

@ -12,7 +12,9 @@ use dbus::{Connection, BusType, ConnectionItem, Message, Path};
use inotify::{Inotify, WatchMask, WatchDescriptor, Event}; use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
pub enum RealmEvent { pub enum RealmEvent {
Starting(Realm),
Started(Realm), Started(Realm),
Stopping(Realm),
Stopped(Realm), Stopped(Realm),
New(Realm), New(Realm),
Removed(Realm), Removed(Realm),
@ -22,7 +24,9 @@ pub enum RealmEvent {
impl Display for RealmEvent { impl Display for RealmEvent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
RealmEvent::Starting(ref realm) => write!(f, "RealmStarting({})", realm.name()),
RealmEvent::Started(ref realm) => write!(f, "RealmStarted({})", realm.name()), RealmEvent::Started(ref realm) => write!(f, "RealmStarted({})", realm.name()),
RealmEvent::Stopping(ref realm) => write!(f, "RealmStopping({})", realm.name()),
RealmEvent::Stopped(ref realm) => write!(f, "RealmStopped({})", realm.name()), RealmEvent::Stopped(ref realm) => write!(f, "RealmStopped({})", realm.name()),
RealmEvent::New(ref realm) => write!(f, "RealmNew({})", realm.name()), RealmEvent::New(ref realm) => write!(f, "RealmNew({})", realm.name()),
RealmEvent::Removed(ref realm) => write!(f, "RealmRemoved({})", realm.name()), RealmEvent::Removed(ref realm) => write!(f, "RealmRemoved({})", realm.name()),

View File

@ -194,7 +194,13 @@ impl RealmManager {
return Ok(()); return Ok(());
} }
info!("Starting realm {}", realm.name()); info!("Starting realm {}", realm.name());
self._start_realm(realm, &mut HashSet::new())?; self.inner().events.send_event(RealmEvent::Starting(realm.clone()));
if let Err(err) = self._start_realm(realm, &mut HashSet::new()) {
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
return Err(err);
}
self.inner().events.send_event(RealmEvent::Started(realm.clone()));
if !Realms::is_some_realm_current() { if !Realms::is_some_realm_current() {
self.inner_mut().realms.set_realm_current(realm) self.inner_mut().realms.set_realm_current(realm)
@ -292,6 +298,7 @@ impl RealmManager {
} }
info!("Stopping realm {}", realm.name()); info!("Stopping realm {}", realm.name());
self.inner().events.send_event(RealmEvent::Stopping(realm.clone()));
if realm.config().flatpak() { if realm.config().flatpak() {
if let Err(err) = self.stop_gnome_software_sandbox(realm) { if let Err(err) = self.stop_gnome_software_sandbox(realm) {
@ -300,8 +307,12 @@ impl RealmManager {
} }
realm.set_active(false); realm.set_active(false);
self.systemd.stop_realm(realm)?; if let Err(err) = self.systemd.stop_realm(realm) {
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
return Err(err);
}
realm.cleanup_rootfs(); realm.cleanup_rootfs();
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
if realm.is_current() { if realm.is_current() {
self.choose_some_current_realm(); self.choose_some_current_realm();

View File

@ -169,6 +169,20 @@ impl Realm {
self.inner.write().unwrap() self.inner.write().unwrap()
} }
pub fn start(&self) -> Result<()> {
warn!("Realm({})::start()", self.name());
self.manager().start_realm(self)
}
pub fn stop(&self) -> Result<()> {
self.manager().stop_realm(self)
}
pub fn set_current(&self) -> Result<()> {
self.manager().set_current_realm(self)
}
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
self.inner_mut().is_active() self.inner_mut().is_active()
} }

View File

@ -9,7 +9,7 @@ homepage = "https://subgraph.com"
[dependencies] [dependencies]
libcitadel = { path = "../libcitadel" } libcitadel = { path = "../libcitadel" }
rand = "0.8" rand = "0.8"
zvariant = "2.7.0" zvariant = "4.2.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
zbus = "=2.0.0-beta.5" zbus = "4.4.0"
gtk = { version = "0.14.0", features = ["v3_24"] } gtk = { version = "0.14.0", features = ["v3_24"] }

View File

@ -1,10 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use zbus::dbus_proxy; use zvariant::Type;
use zvariant::derive::Type;
use serde::{Serialize,Deserialize}; use serde::{Serialize,Deserialize};
use zbus::proxy;
use zbus::blocking::Connection;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
#[derive(Deserialize,Serialize,Type)] #[derive(Deserialize,Serialize,Type)]
@ -85,10 +85,12 @@ impl RealmConfig {
} }
} }
#[dbus_proxy( #[proxy(
default_service = "com.subgraph.realms", default_service = "com.subgraph.realms",
interface = "com.subgraph.realms.Manager", interface = "com.subgraph.realms.Manager",
default_path = "/com/subgraph/realms" default_path = "/com/subgraph/realms",
gen_blocking = true,
gen_async = false,
)] )]
pub trait RealmsManager { pub trait RealmsManager {
fn get_current(&self) -> zbus::Result<String>; fn get_current(&self) -> zbus::Result<String>;
@ -102,7 +104,7 @@ pub trait RealmsManager {
impl RealmsManagerProxy<'_> { impl RealmsManagerProxy<'_> {
pub fn connect() -> Result<Self> { pub fn connect() -> Result<Self> {
let connection = zbus::Connection::new_system()?; let connection = Connection::system()?;
let proxy = RealmsManagerProxy::new(&connection) let proxy = RealmsManagerProxy::new(&connection)
.map_err(|_| Error::ManagerConnect)?; .map_err(|_| Error::ManagerConnect)?;

View File

@ -6,8 +6,11 @@ edition = "2018"
[dependencies] [dependencies]
libcitadel = { path = "../libcitadel" } libcitadel = { path = "../libcitadel" }
zbus = "=2.0.0-beta.5" async-io = "2.3.2"
zvariant = "2.7.0" blocking = "1.6.1"
event-listener = "5.3.1"
zbus = "4.4.0"
zvariant = "4.2.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1.8" serde_repr = "0.1.8"

View File

@ -1,15 +1,16 @@
use zbus::{Connection, ObjectServer}; use async_io::block_on;
use zbus::blocking::Connection;
use zbus::SignalContext;
use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH, realm_status}; use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH, realm_status};
use libcitadel::{RealmEvent, Realm}; use libcitadel::{RealmEvent, Realm};
pub struct EventHandler { pub struct EventHandler {
connection: Connection, connection: Connection,
realms_server: RealmsManagerServer,
} }
impl EventHandler { impl EventHandler {
pub fn new(connection: Connection, realms_server: RealmsManagerServer) -> Self { pub fn new(connection: Connection) -> Self {
EventHandler { connection, realms_server } EventHandler { connection }
} }
pub fn handle_event(&self, ev: &RealmEvent) { pub fn handle_event(&self, ev: &RealmEvent) {
@ -25,44 +26,49 @@ impl EventHandler {
RealmEvent::New(realm) => self.on_new(realm), RealmEvent::New(realm) => self.on_new(realm),
RealmEvent::Removed(realm) => self.on_removed(realm), RealmEvent::Removed(realm) => self.on_removed(realm),
RealmEvent::Current(realm) => self.on_current(realm.as_ref()), RealmEvent::Current(realm) => self.on_current(realm.as_ref()),
RealmEvent::Starting(_) => Ok(()),
RealmEvent::Stopping(_) => Ok(()),
} }
} }
fn with_server<F>(&self, func: F) -> zbus::Result<()> fn with_signal_context<F>(&self, func: F) -> zbus::Result<()>
where where
F: Fn(&RealmsManagerServer) -> zbus::Result<()>, F: Fn(&SignalContext) -> zbus::Result<()>,
{ {
let mut object_server = ObjectServer::new(&self.connection); let object_server = self.connection.object_server();
object_server.at(REALMS_SERVER_OBJECT_PATH, self.realms_server.clone())?; let iface = object_server.interface::<_, RealmsManagerServer>(REALMS_SERVER_OBJECT_PATH)?;
object_server.with(REALMS_SERVER_OBJECT_PATH, |iface: &RealmsManagerServer| func(iface))
let ctx = iface.signal_context();
func(ctx)
} }
fn on_started(&self, realm: &Realm) -> zbus::Result<()> { fn on_started(&self, realm: &Realm) -> zbus::Result<()> {
let pid_ns = realm.pid_ns().unwrap_or(0); let pid_ns = realm.pid_ns().unwrap_or(0);
let status = realm_status(realm); let status = realm_status(realm);
self.with_server(|server| server.realm_started(realm.name(), pid_ns, status)) self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_started(ctx, realm.name(), pid_ns, status)))
} }
fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> { fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> {
let status = realm_status(realm); let status = realm_status(realm);
self.with_server(|server| server.realm_stopped(realm.name(), status)) self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_stopped(ctx, realm.name(), status)))
} }
fn on_new(&self, realm: &Realm) -> zbus::Result<()> { fn on_new(&self, realm: &Realm) -> zbus::Result<()> {
let status = realm_status(realm); let status = realm_status(realm);
let description = realm.notes().unwrap_or(String::new()); let description = realm.notes().unwrap_or(String::new());
self.with_server(|server| server.realm_new(realm.name(), &description, status)) self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_new(ctx, realm.name(), &description, status)))
} }
fn on_removed(&self, realm: &Realm) -> zbus::Result<()> { fn on_removed(&self, realm: &Realm) -> zbus::Result<()> {
self.with_server(|server| server.realm_removed(realm.name())) self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_removed(ctx, realm.name())))
} }
fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> { fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> {
self.with_server(|server| { self.with_signal_context(|ctx| {
match realm { match realm {
Some(realm) => server.realm_current(realm.name(), realm_status(realm)), Some(realm) => block_on(RealmsManagerServer::realm_current(ctx, realm.name(), realm_status(realm))),
None => server.realm_current("", 0), None => block_on(RealmsManagerServer::realm_current(ctx, "", 0)),
} }
}) })
} }

View File

@ -1,14 +1,18 @@
#[macro_use] extern crate libcitadel; #[macro_use] extern crate libcitadel;
use zbus::{Connection, fdo}; use std::env;
use std::sync::Arc;
use libcitadel::{Logger, LogLevel, Result}; use event_listener::{Event, Listener};
use zbus::blocking::Connection;
use crate::realms_manager::RealmsManagerServer; use zbus::fdo::ObjectManager;
use libcitadel::{Logger, LogLevel, Result, RealmManager};
use crate::next::{RealmsManagerServer2, REALMS2_SERVER_OBJECT_PATH};
use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH};
mod realms_manager; mod realms_manager;
mod events; mod events;
mod next;
fn main() { fn main() {
if let Err(e) = run_realm_manager() { if let Err(e) = run_realm_manager() {
@ -16,24 +20,43 @@ fn main() {
} }
} }
fn create_system_connection() -> zbus::Result<Connection> { fn register_realms_manager_server(connection: &Connection, realm_manager: &Arc<RealmManager>, quit_event: &Arc<Event>) -> Result<()> {
let connection = zbus::Connection::new_system()?; let server = RealmsManagerServer::load(&connection, realm_manager.clone(), quit_event.clone())
fdo::DBusProxy::new(&connection)?.request_name("com.subgraph.realms", fdo::RequestNameFlags::AllowReplacement.into())?; .map_err(context!("Loading realms server"))?;
Ok(connection) connection.object_server().at(REALMS_SERVER_OBJECT_PATH, server).map_err(context!("registering realms manager object"))?;
Ok(())
}
fn register_realms2_manager_server(connection: &Connection, realm_manager: &Arc<RealmManager>, quit_event: &Arc<Event>) -> Result<()> {
let server2 = RealmsManagerServer2::load(&connection, realm_manager.clone(), quit_event.clone())
.map_err(context!("Loading realms2 server"))?;
connection.object_server().at(REALMS2_SERVER_OBJECT_PATH, server2).map_err(context!("registering realms manager object"))?;
connection.object_server().at(REALMS2_SERVER_OBJECT_PATH, ObjectManager).map_err(context!("registering ObjectManager"))?;
Ok(())
} }
fn run_realm_manager() -> Result<()> { fn run_realm_manager() -> Result<()> {
Logger::set_log_level(LogLevel::Verbose); Logger::set_log_level(LogLevel::Verbose);
let connection = create_system_connection() let testing = env::args().skip(1).any(|s| s == "--testing");
let connection = Connection::system()
.map_err(context!("ZBus Connection error"))?; .map_err(context!("ZBus Connection error"))?;
let mut object_server = RealmsManagerServer::register(&connection)?;
loop { let realm_manager = RealmManager::load()?;
if let Err(err) = object_server.try_handle_next() { let quit_event = Arc::new(Event::new());
warn!("Error handling DBus message: {}", err);
}
}
if testing {
register_realms2_manager_server(&connection, &realm_manager, &quit_event)?;
connection.request_name("com.subgraph.Realms2")
.map_err(context!("acquiring realms manager name"))?;
} else {
register_realms_manager_server(&connection, &realm_manager, &quit_event)?;
register_realms2_manager_server(&connection, &realm_manager, &quit_event)?;
connection.request_name("com.subgraph.realms")
.map_err(context!("acquiring realms manager name"))?;
};
quit_event.listen().wait();
Ok(())
} }

201
realmsd/src/next/config.rs Normal file
View File

@ -0,0 +1,201 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use serde::{Deserialize, Serialize};
use zbus::fdo;
use zvariant::Type;
use libcitadel::{OverlayType, Realm, GLOBAL_CONFIG};
use libcitadel::terminal::Base16Scheme;
use crate::next::manager::failed;
const BOOL_CONFIG_VARS: &[&str] = &[
"use-gpu", "use-wayland", "use-x11", "use-sound",
"use-shared-dir", "use-network", "use-kvm", "use-ephemeral-home",
"use-media-dir", "use-fuse", "use-flatpak", "use-gpu-card0"
];
fn is_bool_config_variable(variable: &str) -> bool {
BOOL_CONFIG_VARS.iter().any(|&s| s == variable)
}
#[derive(Deserialize,Serialize,Type)]
pub struct RealmConfigVars {
items: HashMap<String,String>,
}
impl RealmConfigVars {
fn new() -> Self {
RealmConfigVars { items: HashMap::new() }
}
pub fn new_global() -> Self {
Self::new_from_config(&GLOBAL_CONFIG)
}
fn new_from_realm(realm: &Realm) -> Self {
let config = realm.config();
Self::new_from_config(&config)
}
fn new_from_config(config: &libcitadel::RealmConfig) -> Self {
let mut vars = RealmConfigVars::new();
vars.add_bool("use-gpu", config.gpu());
vars.add_bool("use-gpu-card0", config.gpu_card0());
vars.add_bool("use-wayland", config.wayland());
vars.add_bool("use-x11", config.x11());
vars.add_bool("use-sound", config.sound());
vars.add_bool("use-shared-dir", config.shared_dir());
vars.add_bool("use-network", config.network());
vars.add_bool("use-kvm", config.kvm());
vars.add_bool("use-ephemeral-home", config.ephemeral_home());
vars.add_bool("use-media-dir", config.media_dir());
vars.add_bool("use-fuse", config.fuse());
vars.add_bool("use-flatpak", config.flatpak());
let overlay = match config.overlay() {
OverlayType::None => "none",
OverlayType::TmpFS => "tmpfs",
OverlayType::Storage => "storage",
};
vars.add("overlay", overlay);
let scheme = match config.terminal_scheme() {
Some(name) => name.to_string(),
None => String::new(),
};
vars.add("terminal-scheme", scheme);
vars.add("realmfs", config.realmfs());
vars
}
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<S,T>(&mut self, k: S, v: T) where S: Into<String>, T: Into<String> {
self.items.insert(k.into(), v.into());
}
}
#[derive(Clone)]
pub struct RealmConfig {
realm: Realm,
changed: Arc<AtomicBool>,
}
impl RealmConfig {
pub fn new(realm: Realm) -> Self {
let changed = Arc::new(AtomicBool::new(false));
RealmConfig { realm, changed }
}
fn mark_changed(&self) {
self.changed.store(true, Ordering::Relaxed);
}
fn is_changed(&self) -> bool {
self.changed.load(Ordering::Relaxed)
}
pub fn config_vars(&self) -> RealmConfigVars {
RealmConfigVars::new_from_realm(&self.realm)
}
fn set_bool_var(&mut self, var: &str, value: &str) -> fdo::Result<()> {
let v = match value {
"true" => true,
"false" => false,
_ => return failed(format!("Invalid boolean value '{}' for realm config variable '{}'", value, var)),
};
let mut has_changed = true;
self.realm.with_mut_config(|c| {
match var {
"use-gpu" if c.gpu() != v => c.use_gpu = Some(v),
_ => has_changed = false,
}
});
if has_changed {
self.mark_changed();
}
Ok(())
}
fn set_overlay(&mut self, value: &str) -> fdo::Result<()> {
let val = match value {
"tmpfs" => Some("tmpfs".to_string()),
"storage" => Some("storage".to_string()),
"none" => None,
_ => return failed(format!("Invalid value '{}' for overlay config", value)),
};
if self.realm.config().overlay != val {
self.realm.with_mut_config(|c| {
c.overlay = Some(value.to_string());
});
self.mark_changed();
}
Ok(())
}
fn set_terminal_scheme(&mut self, value: &str) -> fdo::Result<()> {
if Some(value) == self.realm.config().terminal_scheme() {
return Ok(())
}
let scheme = match Base16Scheme::by_name(value) {
Some(scheme) => scheme,
None => return failed(format!("Invalid terminal color scheme '{}'", value)),
};
let manager = self.realm.manager();
if let Err(err) = scheme.apply_to_realm(&manager, &self.realm) {
return failed(format!("Error applying terminal color scheme: {}", err));
}
self.realm.with_mut_config(|c| {
c.terminal_scheme = Some(value.to_string());
});
self.mark_changed();
Ok(())
}
fn set_realmfs(&mut self, value: &str) -> fdo::Result<()> {
let manager = self.realm.manager();
if manager.realmfs_by_name(value).is_none() {
return failed(format!("Failed to set 'realmfs' config for realm-{}: RealmFS named '{}' does not exist", self.realm.name(), value));
}
if self.realm.config().realmfs() != value {
self.realm.with_mut_config(|c| {
c.realmfs = Some(value.to_string())
});
self.mark_changed();
}
Ok(())
}
pub fn save_config(&self) -> fdo::Result<()> {
if self.is_changed() {
self.realm.config()
.write()
.map_err(|err| fdo::Error::Failed(format!("Error writing config file for realm-{}: {}", self.realm.name(), err)))?;
self.changed.store(false, Ordering::Relaxed);
}
Ok(())
}
pub fn set_var(&mut self, var: &str, value: &str) -> fdo::Result<()> {
if is_bool_config_variable(var) {
self.set_bool_var(var, value)
} else if var == "overlay" {
self.set_overlay(value)
} else if var == "terminal-scheme" {
self.set_terminal_scheme(value)
} else if var == "realmfs" {
self.set_realmfs(value)
} else {
failed(format!("Unknown realm configuration variable '{}'", var))
}
}
}

103
realmsd/src/next/manager.rs Normal file
View File

@ -0,0 +1,103 @@
use std::sync::Arc;
use blocking::unblock;
use event_listener::{Event, EventListener};
use serde::Serialize;
use serde_repr::Serialize_repr;
use zbus::blocking::Connection;
use zbus::{fdo, interface};
use zvariant::Type;
use libcitadel::{PidLookupResult, RealmManager};
use crate::next::config::RealmConfigVars;
use crate::next::realm::RealmItemState;
use crate::next::realmfs::RealmFSState;
pub fn failed<T>(message: String) -> fdo::Result<T> {
Err(fdo::Error::Failed(message))
}
#[derive(Serialize_repr, Type, Debug, PartialEq)]
#[repr(u32)]
pub enum PidLookupResultCode {
Unknown = 1,
Realm = 2,
Citadel = 3,
}
#[derive(Debug, Type, Serialize)]
pub struct RealmFromCitadelPid {
code: PidLookupResultCode,
realm: String,
}
impl From<PidLookupResult> for RealmFromCitadelPid {
fn from(result: PidLookupResult) -> Self {
match result {
PidLookupResult::Unknown => RealmFromCitadelPid { code: PidLookupResultCode::Unknown, realm: String::new() },
PidLookupResult::Realm(realm) => RealmFromCitadelPid { code: PidLookupResultCode::Realm, realm: realm.name().to_string() },
PidLookupResult::Citadel => RealmFromCitadelPid { code: PidLookupResultCode::Citadel, realm: String::new() },
}
}
}
pub struct RealmsManagerServer2 {
realms: RealmItemState,
realmfs_state: RealmFSState,
manager: Arc<RealmManager>,
quit_event: Arc<Event>,
}
impl RealmsManagerServer2 {
fn new(connection: Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> Self {
let realms = RealmItemState::new(connection.clone());
let realmfs_state = RealmFSState::new(connection.clone());
RealmsManagerServer2 {
realms,
realmfs_state,
manager,
quit_event,
}
}
pub fn load(connection: &Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> zbus::Result<Self> {
let mut server = Self::new(connection.clone(), manager.clone(), quit_event);
server.realms.load_realms(&manager)?;
server.realmfs_state.load(&manager)?;
server.realms.populate_realmfs(&server.realmfs_state)?;
Ok(server)
}
}
#[interface(name = "com.subgraph.realms.Manager2")]
impl RealmsManagerServer2 {
async fn get_current(&self) -> u32 {
self.realms.get_current()
.map(|r| r.index())
.unwrap_or(0)
}
async fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid {
let manager = self.manager.clone();
unblock(move || {
manager.realm_by_pid(pid).into()
}).await
}
async fn create_realm(&self, name: &str) -> fdo::Result<()> {
let manager = self.manager.clone();
let name = name.to_string();
unblock(move || {
let _ = manager.new_realm(&name).map_err(|err| fdo::Error::Failed(err.to_string()))?;
Ok(())
}).await
}
async fn get_global_config(&self) -> RealmConfigVars {
RealmConfigVars::new_global()
}
}

8
realmsd/src/next/mod.rs Normal file
View File

@ -0,0 +1,8 @@
mod manager;
mod config;
mod realm;
mod realmfs;
pub use manager::RealmsManagerServer2;
pub const REALMS2_SERVER_OBJECT_PATH: &str = "/com/subgraph/Realms2";

374
realmsd/src/next/realm.rs Normal file
View File

@ -0,0 +1,374 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::{Arc, Mutex, MutexGuard};
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, Ordering};
use blocking::unblock;
use zbus::{interface, fdo};
use zbus::blocking::Connection;
use zbus::names::{BusName, InterfaceName};
use zvariant::{OwnedObjectPath, Value};
use libcitadel::{Realm, RealmEvent, RealmManager, Result};
use crate::next::config::{RealmConfig, RealmConfigVars};
use crate::next::realmfs::RealmFSState;
use crate::next::REALMS2_SERVER_OBJECT_PATH;
#[derive(Clone)]
pub struct RealmItem {
path: String,
index: u32,
realm: Realm,
config: RealmConfig,
in_run_transition: Arc<AtomicBool>,
realmfs_index: Arc<AtomicU32>,
last_timestamp: Arc<AtomicI64>,
}
#[derive(Copy,Clone)]
#[repr(u32)]
enum RealmRunStatus {
Stopped = 0,
Starting,
Running,
Current,
Stopping,
}
impl RealmRunStatus {
fn for_realm(realm: &Realm, in_transition: bool) -> Self {
if in_transition {
if realm.is_active() { Self::Stopping } else { Self::Starting }
} else if realm.is_active() {
if realm.is_current() { Self::Current } else {Self::Running }
} else {
Self::Stopped
}
}
}
impl RealmItem {
pub(crate) fn new_from_realm(index: u32, realm: Realm) -> RealmItem {
let path = format!("{}/Realm{}", REALMS2_SERVER_OBJECT_PATH, index);
let in_run_transition = Arc::new(AtomicBool::new(false));
let config = RealmConfig::new(realm.clone());
let realmfs_index = Arc::new(AtomicU32::new(0));
let last_timestamp = Arc::new(AtomicI64::new(realm.timestamp()));
RealmItem { path, index, realm, config, in_run_transition, realmfs_index, last_timestamp }
}
pub fn path(&self) -> &str {
&self.path
}
pub fn index(&self) -> u32 {
self.index
}
fn in_run_transition(&self) -> bool {
self.in_run_transition.load(Ordering::Relaxed)
}
fn get_run_status(&self) -> RealmRunStatus {
RealmRunStatus::for_realm(&self.realm, self.in_run_transition())
}
async fn do_start(&mut self) -> fdo::Result<()> {
if !self.realm.is_active() {
let realm = self.realm.clone();
let res = unblock(move || realm.start()).await;
if let Err(err) = res {
return Err(fdo::Error::Failed(format!("Failed to start realm: {}", err)));
}
}
Ok(())
}
async fn do_stop(&mut self) -> fdo::Result<()> {
if self.realm.is_active() {
let realm = self.realm.clone();
let res = unblock(move || realm.stop()).await;
if let Err(err) = res {
return Err(fdo::Error::Failed(format!("Failed to stop realm: {}", err)));
}
}
Ok(())
}
}
#[interface(
name = "com.subgraph.realms.Realm"
)]
impl RealmItem {
async fn start(
&mut self,
) -> fdo::Result<()> {
self.do_start().await?;
Ok(())
}
async fn stop(
&mut self,
) -> fdo::Result<()> {
self.do_stop().await?;
Ok(())
}
async fn restart(
&mut self,
) -> fdo::Result<()> {
self.do_stop().await?;
self.do_start().await?;
Ok(())
}
async fn set_current(&mut self) -> fdo::Result<()> {
let realm = self.realm.clone();
let res = unblock(move || realm.set_current()).await;
if let Err(err) = res {
return Err(fdo::Error::Failed(format!("Failed to set realm {} as current: {}", self.realm.name(), err)));
}
Ok(())
}
async fn get_config(&self) -> RealmConfigVars {
self.config.config_vars()
}
async fn set_config(&mut self, vars: Vec<(String, String)>) -> fdo::Result<()> {
for (var, val) in &vars {
self.config.set_var(var, val)?;
}
let config = self.config.clone();
unblock(move || config.save_config()).await
}
#[zbus(property, name = "RunStatus")]
fn run_status(&self) -> u32 {
self.get_run_status() as u32
}
#[zbus(property, name="IsSystemRealm")]
fn is_system_realm(&self) -> bool {
self.realm.is_system()
}
#[zbus(property, name = "Name")]
fn name(&self) -> &str {
self.realm.name()
}
#[zbus(property, name = "Description")]
fn description(&self) -> String {
self.realm.notes()
.unwrap_or(String::new())
}
#[zbus(property, name = "PidNS")]
fn pid_ns(&self) -> u64 {
self.realm.pid_ns().unwrap_or_default()
}
#[zbus(property, name = "RealmFS")]
fn realmfs(&self) -> u32 {
self.realmfs_index.load(Ordering::Relaxed)
}
#[zbus(property, name = "Timestamp")]
fn timestamp(&self) -> u64 {
self.realm.timestamp() as u64
}
}
#[derive(Clone)]
pub struct RealmItemState(Arc<Mutex<Inner>>);
struct Inner {
connection: Connection,
next_index: u32,
realms: HashMap<String, RealmItem>,
current_realm: Option<RealmItem>,
}
impl Inner {
fn new(connection: Connection) -> Self {
Inner {
connection,
next_index: 1,
realms:HashMap::new(),
current_realm: None,
}
}
fn load_realms(&mut self, manager: &RealmManager) -> zbus::Result<()> {
for realm in manager.realm_list() {
self.add_realm(realm)?;
}
Ok(())
}
pub fn populate_realmfs(&mut self, realmfs_state: &RealmFSState) -> zbus::Result<()> {
for item in self.realms.values_mut() {
if let Some(realmfs) = realmfs_state.realmfs_by_name(item.realm.config().realmfs()) {
item.realmfs_index.store(realmfs.index(), Ordering::Relaxed);
}
}
Ok(())
}
fn add_realm(&mut self, realm: Realm) -> zbus::Result<()> {
if self.realms.contains_key(realm.name()) {
warn!("Attempted to add duplicate realm '{}'", realm.name());
return Ok(())
}
let key = realm.name().to_string();
let item = RealmItem::new_from_realm(self.next_index, realm);
self.connection.object_server().at(item.path(), item.clone())?;
self.realms.insert(key, item);
self.next_index += 1;
Ok(())
}
fn remove_realm(&mut self, realm: &Realm) -> zbus::Result<()> {
if let Some(item) = self.realms.remove(realm.name()) {
self.connection.object_server().remove::<RealmItem, &str>(item.path())?;
} else {
warn!("Failed to find realm to remove with name '{}'", realm.name());
}
Ok(())
}
fn emit_property_changed(&self, object_path: OwnedObjectPath, propname: &str, value: Value<'_>) -> zbus::Result<()> {
let iface_name = InterfaceName::from_str_unchecked("com.subgraph.realms.Realm");
let changed = HashMap::from([(propname.to_string(), value)]);
let inval: &[&str] = &[];
self.connection.emit_signal(
None::<BusName<'_>>,
&object_path,
"org.freedesktop.Dbus.Properties",
"PropertiesChanged",
&(iface_name, changed, inval))?;
Ok(())
}
fn realm_status_changed(&self, realm: &Realm, transition: Option<bool>) -> zbus::Result<()> {
if let Some(realm) = self.realm_by_name(realm.name()) {
if let Some(transition) = transition {
realm.in_run_transition.store(transition, Ordering::Relaxed);
}
let object_path = realm.path().try_into().unwrap();
self.emit_property_changed(object_path, "RunStatus", Value::U32(realm.get_run_status() as u32))?;
let timestamp = realm.realm.timestamp();
if realm.last_timestamp.load(Ordering::Relaxed) != realm.realm.timestamp() {
realm.last_timestamp.store(timestamp, Ordering::Relaxed);
let object_path = realm.path().try_into().unwrap();
self.emit_property_changed(object_path, "Timestamp", Value::U64(timestamp as u64))?;
}
}
Ok(())
}
fn realm_by_name(&self, name: &str) -> Option<&RealmItem> {
let res = self.realms.get(name);
if res.is_none() {
warn!("Failed to find realm with name '{}'", name);
}
res
}
fn on_starting(&self, realm: &Realm) -> zbus::Result<()>{
self.realm_status_changed(realm, Some(true))?;
Ok(())
}
fn on_started(&self, realm: &Realm) -> zbus::Result<()>{
self.realm_status_changed(realm, Some(false))
}
fn on_stopping(&self, realm: &Realm) -> zbus::Result<()> {
self.realm_status_changed(realm, Some(true))
}
fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> {
self.realm_status_changed(realm, Some(false))
}
fn on_new(&mut self, realm: &Realm) -> zbus::Result<()> {
self.add_realm(realm.clone())?;
Ok(())
}
fn on_removed(&mut self, realm: &Realm) -> zbus::Result<()> {
self.remove_realm(&realm)?;
Ok(())
}
fn on_current(&mut self, realm: Option<&Realm>) -> zbus::Result<()> {
if let Some(r) = self.current_realm.take() {
self.realm_status_changed(&r.realm, None)?;
}
if let Some(realm) = realm {
self.realm_status_changed(realm, None)?;
if let Some(item) = self.realm_by_name(realm.name()) {
self.current_realm = Some(item.clone());
}
}
Ok(())
}
}
impl RealmItemState {
pub fn new(connection: Connection) -> Self {
RealmItemState(Arc::new(Mutex::new(Inner::new(connection))))
}
pub fn load_realms(&self, manager: &RealmManager) -> zbus::Result<()> {
self.inner().load_realms(manager)?;
self.add_event_handler(manager)
.map_err(|err| zbus::Error::Failure(err.to_string()))?;
Ok(())
}
pub fn populate_realmfs(&self, realmfs_state: &RealmFSState) -> zbus::Result<()> {
self.inner().populate_realmfs(realmfs_state)
}
pub fn get_current(&self) -> Option<RealmItem> {
self.inner().current_realm.clone()
}
fn inner(&self) -> MutexGuard<Inner> {
self.0.lock().unwrap()
}
fn add_event_handler(&self, manager: &RealmManager) -> Result<()> {
let state = self.clone();
manager.add_event_handler(move |ev| {
if let Err(err) = state.handle_event(ev) {
warn!("Error handling {}: {}", ev, err);
}
});
manager.start_event_task()?;
Ok(())
}
fn handle_event(&self, ev: &RealmEvent) -> zbus::Result<()> {
match ev {
RealmEvent::Started(realm) => self.inner().on_started(realm)?,
RealmEvent::Stopped(realm) => self.inner().on_stopped(realm)?,
RealmEvent::New(realm) => self.inner().on_new(realm)?,
RealmEvent::Removed(realm) => self.inner().on_removed(realm)?,
RealmEvent::Current(realm) => self.inner().on_current(realm.as_ref())?,
RealmEvent::Starting(realm) => self.inner().on_starting(realm)?,
RealmEvent::Stopping(realm) => self.inner().on_stopping(realm)?,
};
Ok(())
}
}

120
realmsd/src/next/realmfs.rs Normal file
View File

@ -0,0 +1,120 @@
use std::collections::HashMap;
use std::convert::TryInto;
use zbus::blocking::Connection;
use zbus::{fdo, interface};
use zvariant::{ObjectPath, OwnedObjectPath};
use libcitadel::{RealmFS, RealmManager};
use crate::next::REALMS2_SERVER_OBJECT_PATH;
const BLOCK_SIZE: u64 = 4096;
#[derive(Clone)]
pub struct RealmFSItem {
object_path: OwnedObjectPath,
index: u32,
realmfs: RealmFS,
}
impl RealmFSItem {
pub(crate) fn new_from_realmfs(index: u32, realmfs: RealmFS) -> RealmFSItem {
let object_path = format!("{}/RealmFS{}", REALMS2_SERVER_OBJECT_PATH, index).try_into().unwrap();
RealmFSItem {
object_path,
index,
realmfs,
}
}
pub fn index(&self) -> u32 {
self.index
}
pub fn object_path(&self) -> ObjectPath {
self.object_path.as_ref()
}
}
#[interface(
name = "com.subgraph.realms.RealmFS"
)]
impl RealmFSItem {
#[zbus(property, name = "Name")]
fn name(&self) -> &str {
self.realmfs.name()
}
#[zbus(property, name = "Activated")]
fn activated(&self) -> bool {
self.realmfs.is_activated()
}
#[zbus(property, name = "InUse")]
fn in_use(&self) -> bool {
self.realmfs.is_activated()
}
#[zbus(property, name = "Mountpoint")]
fn mountpoint(&self) -> String {
self.realmfs.mountpoint().to_string()
}
#[zbus(property, name = "Path")]
fn path(&self) -> String {
format!("{}", self.realmfs.path().display())
}
#[zbus(property, name = "FreeSpace")]
fn free_space(&self) -> fdo::Result<u64> {
let blocks = self.realmfs.free_size_blocks()
.map_err(|err| fdo::Error::Failed(err.to_string()))?;
Ok(blocks as u64 * BLOCK_SIZE)
}
#[zbus(property, name = "AllocatedSpace")]
fn allocated_space(&self) -> fdo::Result<u64> {
let blocks = self.realmfs.allocated_size_blocks()
.map_err(|err| fdo::Error::Failed(err.to_string()))?;
Ok(blocks as u64 * BLOCK_SIZE)
}
}
pub struct RealmFSState {
connection: Connection,
next_index: u32,
items: HashMap<String, RealmFSItem>,
}
impl RealmFSState {
pub fn new(connection: Connection) -> Self {
RealmFSState {
connection,
next_index: 1,
items: HashMap::new(),
}
}
pub fn load(&mut self, manager: &RealmManager) -> zbus::Result<()> {
for realmfs in manager.realmfs_list() {
self.add_realmfs(realmfs)?;
}
Ok(())
}
fn add_realmfs(&mut self, realmfs: RealmFS) -> zbus::Result<()> {
if !self.items.contains_key(realmfs.name()) {
let name = realmfs.name().to_string();
let item = RealmFSItem::new_from_realmfs(self.next_index, realmfs);
self.connection.object_server().at(item.object_path(), item.clone())?;
self.items.insert(name, item);
self.next_index += 1;
} else {
warn!("Attempted to add duplicate realmfs '{}'", realmfs.name());
}
Ok(())
}
pub fn realmfs_by_name(&self, name: &str) -> Option<&RealmFSItem> {
let res = self.items.get(name);
if res.is_none() {
warn!("Failed to find RealmFS with name '{}'", name);
}
res
}
}

View File

@ -1,11 +1,14 @@
use libcitadel::{RealmManager, Realm, OverlayType, Result, PidLookupResult}; use libcitadel::{RealmManager, Realm, OverlayType, Result, PidLookupResult};
use std::sync::Arc; use std::sync::Arc;
use zbus::{dbus_interface, ObjectServer,Connection}; use zbus::blocking::Connection;
use zvariant::derive::Type; use zvariant::Type;
use std::thread; use std::thread;
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Serialize,Deserialize}; use blocking::unblock;
use event_listener::{Event, EventListener};
use serde::{Serialize, Deserialize};
use serde_repr::Serialize_repr; use serde_repr::Serialize_repr;
use zbus::{interface, SignalContext};
use crate::events::EventHandler; use crate::events::EventHandler;
use libcitadel::terminal::Base16Scheme; use libcitadel::terminal::Base16Scheme;
@ -39,6 +42,7 @@ impl From<PidLookupResult> for RealmFromCitadelPid {
#[derive(Clone)] #[derive(Clone)]
pub struct RealmsManagerServer { pub struct RealmsManagerServer {
manager: Arc<RealmManager>, manager: Arc<RealmManager>,
quit_event: Arc<Event>,
} }
const BOOL_CONFIG_VARS: &[&str] = &[ const BOOL_CONFIG_VARS: &[&str] = &[
@ -121,40 +125,40 @@ fn configure_realm(manager: &RealmManager, realm: &Realm, variable: &str, value:
impl RealmsManagerServer { impl RealmsManagerServer {
fn register_events(&self, connection: &Connection) -> Result<()> { pub fn load(connection: &Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> Result<RealmsManagerServer> {
let events = EventHandler::new(connection.clone(), self.clone()); let server = RealmsManagerServer { manager, quit_event };
self.manager.add_event_handler(move |ev| events.handle_event(ev)); let events = EventHandler::new(connection.clone());
self.manager.start_event_task() server.manager.add_event_handler(move |ev| events.handle_event(ev));
server.manager.start_event_task()?;
Ok(server)
} }
pub fn register(connection: &Connection) -> Result<ObjectServer> {
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")] #[interface(name = "com.subgraph.realms.Manager")]
impl RealmsManagerServer { impl RealmsManagerServer {
fn set_current(&self, name: &str) { async 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) { let manager = self.manager.clone();
let name = name.to_string();
unblock(move || {
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); warn!("set_current_realm({}) failed: {}", name, err);
} }
} }
}).await
} }
fn get_current(&self) -> String { async fn get_current(&self) -> String {
match self.manager.current_realm() { let manager = self.manager.clone();
unblock(move || {
match manager.current_realm() {
Some(realm) => realm.name().to_string(), Some(realm) => realm.name().to_string(),
None => String::new(), None => String::new(),
} }
}).await
} }
fn list(&self) -> Vec<RealmItem> { fn list(&self) -> Vec<RealmItem> {
@ -249,8 +253,12 @@ impl RealmsManagerServer {
}); });
} }
fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid { async fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid {
self.manager.realm_by_pid(pid).into() let manager = self.manager.clone();
unblock(move || {
manager.realm_by_pid(pid).into()
}).await
} }
fn realm_config(&self, name: &str) -> RealmConfig { fn realm_config(&self, name: &str) -> RealmConfig {
@ -261,7 +269,7 @@ impl RealmsManagerServer {
RealmConfig::new_from_realm(&realm) RealmConfig::new_from_realm(&realm)
} }
fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) { async fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) {
let realm = match self.manager.realm_by_name(name) { let realm = match self.manager.realm_by_name(name) {
Some(r) => r, Some(r) => r,
None => { None => {
@ -270,8 +278,12 @@ impl RealmsManagerServer {
}, },
}; };
for var in &vars { for var in vars {
configure_realm(&self.manager, &realm, &var.0, &var.1); let manager = self.manager.clone();
let realm = realm.clone();
unblock( move || {
configure_realm(&manager, &realm, &var.0, &var.1);
}).await;
} }
} }
@ -279,13 +291,18 @@ impl RealmsManagerServer {
Realm::is_valid_name(name) && self.manager.realm_by_name(name).is_some() Realm::is_valid_name(name) && self.manager.realm_by_name(name).is_some()
} }
fn create_realm(&self, name: &str) -> bool { async fn create_realm(&self, name: &str) -> bool {
if let Err(err) = self.manager.new_realm(name) {
let manager = self.manager.clone();
let name = name.to_string();
unblock(move || {
if let Err(err) = manager.new_realm(&name) {
warn!("Error creating realm ({}): {}", name, err); warn!("Error creating realm ({}): {}", name, err);
false false
} else { } else {
true true
} }
}).await
} }
fn list_realm_f_s(&self) -> Vec<String> { fn list_realm_f_s(&self) -> Vec<String> {
@ -299,23 +316,23 @@ impl RealmsManagerServer {
} }
#[dbus_interface(signal)] #[zbus(signal)]
pub fn realm_started(&self, realm: &str, pid_ns: u64, status: u8) -> zbus::Result<()> { Ok(()) } pub async fn realm_started(ctx: &SignalContext<'_>, realm: &str, pid_ns: u64, status: u8) -> zbus::Result<()>;
#[dbus_interface(signal)] #[zbus(signal)]
pub fn realm_stopped(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) } pub async fn realm_stopped(ctx: &SignalContext<'_>, realm: &str, status: u8) -> zbus::Result<()>;
#[dbus_interface(signal)] #[zbus(signal)]
pub fn realm_new(&self, realm: &str, description: &str, status: u8) -> zbus::Result<()> { Ok(()) } pub async fn realm_new(ctx: &SignalContext<'_>, realm: &str, description: &str, status: u8) -> zbus::Result<()>;
#[dbus_interface(signal)] #[zbus(signal)]
pub fn realm_removed(&self, realm: &str) -> zbus::Result<()> { Ok(()) } pub async fn realm_removed(ctx: &SignalContext<'_>, realm: &str) -> zbus::Result<()>;
#[dbus_interface(signal)] #[zbus(signal)]
pub fn realm_current(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) } pub async fn realm_current(ctx: &SignalContext<'_>, realm: &str, status: u8) -> zbus::Result<()>;
#[dbus_interface(signal)] #[zbus(signal)]
pub fn service_started(&self) -> zbus::Result<()> { Ok(()) } pub async fn service_started(ctx: &SignalContext<'_>) -> zbus::Result<()>;
} }