Files
dysm-rs/src/session.rs

435 lines
16 KiB
Rust

//! Session management for the display manager.
//!
//! This module defines the `SessionManager` struct, which is responsible for
//! managing user sessions. It includes functionality
//! for loading configuration, starting and stopping sessions, and
//! handling session-related events.
use crate::configuration::Configuration;
use crate::{APPLICATION_NAME, VERSION};
use gio::{self, DBusConnection, OwnerId, RegistrationId};
use glib::{g_critical, g_debug, g_info, g_warning};
use lazy_static::*;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
static G_DBUS_INTROSPECTION: &str = include_str!("../resources/introspection.xml");
const G_DBUS_SERVER_NAME: &str = "dev.iohub.diya.SessionManager";
const G_DBUS_SERVER_PATH: &str = "/dev/iohub/diya/SessionManager";
const G_DBUS_SERVER_ERROR_NAME: &str = "dev.iohub.diya.SessionManager.Error";
const G_DBUS_METHOD_LOGIN: &str = "login";
const G_PROCESSES_MON_TO: u64 = 500; // Interval for monitoring child processes in milliseconds
lazy_static! {
/// A global instance of the `SessionManager` wrapped in a `Mutex` for thread safety.
/// This allows the session manager to be accessed and modified from different parts of the application
/// while ensuring that only one instance is active at a time.
static ref G_SESSION_MANAGER: Arc<Mutex<SessionManagerState>> = Arc::new(Mutex::<SessionManagerState>::new(SessionManagerState::End));
}
/// SessionManagerState
///
/// An enum representing the state of the session manager. It can be in a running state with an active `SessionManager`,
/// or it can be in an end or reload state.
pub enum SessionManagerState {
/// The session manager is currently running and managing user sessions.
Running(SessionManager),
/// The session manager has been stopped and is no longer managing sessions.
End,
/// The session manager is in the process of reloading its configuration or restarting.
Reload,
}
/// SessionManager
///
/// The `SessionManager` struct is responsible for managing user sessions in the display manager.
pub struct SessionManager {
/// the configuration for the session manager
config: Configuration,
/// The main loop for handling session events.
main_loop: glib::MainLoop,
/// The D-Bus owner ID for the session manager's D-Bus name.
bus_owner_id: Option<OwnerId>,
/// A list of signal handler IDs for any signals the session manager has connected to.
signals: Vec<glib::SourceId>,
/// The D-Bus connection and registration ID for the session manager's D-Bus object.
bus_registration: Option<(DBusConnection, RegistrationId)>,
}
/// Public interface for SessionManager
///
pub trait SessionManagerInterface {
/// Run the session manager with an input config file
///
/// # Arguments
///
/// - `&self` - Object that implements the Trait
/// - `config` (`&PathBuf`) - Config file.
/// - `is_dbus_session` (`bool`) - run the session manager using session DBUS or system DBUS
///
/// # Returns
///
/// - `bool` - if True service shall be restarted after finished
///
fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool;
/// Quit the current session manager service
///
/// # Arguments
///
/// - `&self` - Object that implements the Trait
/// - `reload` (`bool`) - if true the service shall be restarted after finished
///
fn quit(&self, reload: bool);
}
/// Trait that defines callbacks for SessionManager
///
trait SessionManagerCallback {
/// Handler for DBUS name acquired event
///
/// # Arguments
///
/// - `&self` - Object that implements the Trait.
/// - `connection` (`DBusConnection`) - DBUS connection.
/// - `name` (`&str`) - DBUS name.
///
fn on_name_acquired(&self, connection: DBusConnection, name: &str);
/// Handler for DBUS bus acquired event
///
/// # Arguments
///
/// - `&self` - Object that implements this Trait
/// - `connection` (`DBusConnection`) - DBUS connection
/// - `name` (`&str`) - DBUS name
///
fn on_bus_acquired(&self, connection: DBusConnection, name: &str);
/// Hander for DBUS name lost event
///
/// # Arguments
///
/// - `&self` - Object that implements this Trait
/// - `connection` (`Option<DBusConnection>`) - DBUS connection.
/// - `name` (`&str`) - DBUS name.
///
fn on_name_lost(&self, connection: Option<DBusConnection>, name: &str);
/// Handler for DBUS method call event
///
/// # Arguments
///
/// - `&self` (`undefined`) - Object that implements this Trait.
/// - `connection` (`DBusConnection`) - DBUS connection.
/// - `sender` (`Option<&str>`) - Sender name.
/// - `object_path` (`&str`) - DBUS Object path.
/// - `interface_name` (`Option<&str>`) - DBUS interface name.
/// - `method_name` (`&str`) - DBUS method name.
/// - `parameters` (`gio::glib::Variant`) - DBUS method parameters.
/// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object.
///
fn on_method_call(
&self,
connection: DBusConnection,
sender: Option<&str>,
object_path: &str,
interface_name: Option<&str>,
method_name: &str,
parameters: gio::glib::Variant,
invocation: gio::DBusMethodInvocation,
);
/// Hander for service processes monitor
///
/// # Arguments
///
/// - `&self` - Object that implements this Trait
///
fn on_processes_monitor(&self);
}
impl SessionManagerInterface for Arc<Mutex<SessionManagerState>> {
fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool {
self.quit(false);
*self.lock().as_deref_mut().unwrap() = SessionManagerState::Running(SessionManager {
config: Configuration::from(&config).expect("Failed to load configuration"),
main_loop: glib::MainLoop::new(None, false),
bus_owner_id: None,
signals: Vec::new(),
bus_registration: None,
});
let evtloop = match self.lock().as_deref_mut().unwrap() {
SessionManagerState::Running(session) => {
session.setup(is_dbus_session);
session.main_loop.clone()
}
_ => panic!("No SessionManager created"),
};
evtloop.run();
match self.lock().as_deref().unwrap() {
SessionManagerState::Reload => true,
_ => false,
}
}
fn quit(&self, reload: bool) {
if let SessionManagerState::Running(session) = self.lock().as_deref().unwrap() {
session.main_loop.quit();
}
*self.lock().as_deref_mut().unwrap() = if reload {
SessionManagerState::Reload
} else {
SessionManagerState::End
};
}
}
impl SessionManagerCallback for Arc<Mutex<SessionManagerState>> {
fn on_name_acquired(&self, _connection: DBusConnection, name: &str) {
g_debug!(APPLICATION_NAME, "D-Bus name acquired: {}", name);
}
fn on_bus_acquired(&self, connection: DBusConnection, name: &str) {
g_debug!(APPLICATION_NAME, "D-BUS acquired for name {}", name);
let introspection = match gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION) {
Ok(info) => info,
Err(err) => {
g_critical!(
APPLICATION_NAME,
"Failed to parse D-Bus introspection data: {}",
err
);
self.quit(false);
return;
}
};
let interface = &introspection.interfaces()[0];
let registration_id = match connection
.register_object(G_DBUS_SERVER_PATH, interface)
.method_call(
|connection: DBusConnection,
sender: Option<&str>,
object_path: &str,
interface_name: Option<&str>,
method_name: &str,
parameters: gio::glib::Variant,
invocation: gio::DBusMethodInvocation| {
SessionManager::instance().on_method_call(
connection,
sender,
object_path,
interface_name,
method_name,
parameters,
invocation,
);
},
)
.build()
{
Err(error) => {
g_critical!(
APPLICATION_NAME,
"Failed to register D-Bus object at path {}: {}",
G_DBUS_SERVER_PATH,
error
);
self.quit(false);
return;
}
Ok(resource) => resource,
};
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
session.bus_registration = Some((connection, registration_id));
}
}
fn on_name_lost(&self, _connection: Option<DBusConnection>, name: &str) {
g_critical!(APPLICATION_NAME, "D-Bus name lost: {}", name);
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
session.bus_owner_id = None;
}
self.quit(false);
}
fn on_method_call(
&self,
_connection: DBusConnection,
_sender: Option<&str>,
_object_path: &str,
interface_name: Option<&str>,
method_name: &str,
parameters: gio::glib::Variant,
invocation: gio::DBusMethodInvocation,
) {
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
if !matches!(interface_name, Some(G_DBUS_SERVER_NAME)) {
let msg = format!(
"Interface name mismatch: got {}, but expected {}",
interface_name.unwrap(),
G_DBUS_SERVER_NAME
);
g_warning!(APPLICATION_NAME, "{}", msg);
invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg);
return;
}
g_debug!(
APPLICATION_NAME,
"Received method call: {}, interface: {}",
method_name,
interface_name.unwrap()
);
match method_name {
G_DBUS_METHOD_LOGIN => session.login(&parameters, invocation),
_ => {
let msg = format!("Unsupported method {}", method_name);
g_warning!(APPLICATION_NAME, "{}", msg);
invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, &msg);
}
}
}
}
fn on_processes_monitor(&self) {
if let SessionManagerState::Running(session) = self.lock().as_deref_mut().unwrap() {
session.monitor();
}
}
}
impl SessionManager {
/// Get a threadsafe singleton instance of the SessionManager
///
/// # Returns
///
/// - `Arc<Mutex<SessionManagerState>>` - Smart pointer to SessionManager
///
/// # Examples
///
/// ```
/// use dysm_rs::SessionManager;
///
/// let instance = SessionManager::instance();
/// ```
pub fn instance() -> Arc<Mutex<SessionManagerState>> {
G_SESSION_MANAGER.clone()
}
/// Setup the session manager before running
///
/// # Arguments
///
/// - `&mut self` (`&mut SessionManager`) - Mutable reference to SessionManager.
/// - `is_dbus_session` (`bool`) - if true setup service using DBUS session, otherwise SBUS system
///
fn setup(&mut self, is_dbus_session: bool) {
g_info!(
APPLICATION_NAME,
"Starting SessionManager version {}",
VERSION
);
g_debug!(APPLICATION_NAME, "{}", self.config);
g_debug!(APPLICATION_NAME, "Use session DBUS: {}", is_dbus_session);
self.bus_owner_id = Some(gio::bus_own_name(
if is_dbus_session {
gio::BusType::Session
} else {
gio::BusType::System
},
G_DBUS_SERVER_NAME,
gio::BusNameOwnerFlags::NONE,
move |connection, name| {
SessionManager::instance().on_bus_acquired(connection, name);
},
|connection, name| {
SessionManager::instance().on_name_acquired(connection, name);
},
move |connection, name| {
SessionManager::instance().on_name_lost(connection, name);
},
));
for signal in vec![libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
let signal_id = match signal {
libc::SIGHUP => glib::unix_signal_add_local(signal, move || {
g_warning!(APPLICATION_NAME, "Received SIGHUP, reloading service...");
SessionManager::instance().quit(true);
glib::ControlFlow::Continue
}),
_ => glib::unix_signal_add_local(signal, move || {
g_info!(
APPLICATION_NAME,
"Received signal {}, shutting down...",
signal
);
SessionManager::instance().quit(false);
glib::ControlFlow::Continue
}),
};
self.signals.push(signal_id);
}
let processes_monitor_id =
glib::timeout_add_local(Duration::from_millis(G_PROCESSES_MON_TO), move || {
// Monitor child processes here
// For example, you could check if any child processes have exited and handle that accordingly
// g_debug!(APPLICATION_NAME, "Monitoring child processes...");
SessionManager::instance().on_processes_monitor();
glib::ControlFlow::Continue
});
self.signals.push(processes_monitor_id);
// Implementation for running the session manager
}
/// Process DBUS login method cal
///
/// # Arguments
///
/// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager.
/// - `parameters` (`&gio::glib::Variant`) - DBUS method parameters.
/// - `invocation` (`gio::DBusMethodInvocation`) - DBUS method invocation object.
///
fn login(&self, parameters: &gio::glib::Variant, invocation: gio::DBusMethodInvocation) {
g_debug!(APPLICATION_NAME, "Logging called");
invocation.return_dbus_error(G_DBUS_SERVER_ERROR_NAME, "Unimplemented");
}
/// Monitor session processes
///
/// # Arguments
///
/// - `&self` (`&mut SessionManager`) - Mutable reference to current SessionManager.
///
fn monitor(&self) {
// Here you would implement the logic to monitor child processes.
// For example, you could check if any child processes have exited and handle that accordingly.
g_debug!(APPLICATION_NAME, "Monitoring child processes...");
}
}
impl Drop for SessionManager {
fn drop(&mut self) {
g_info!(APPLICATION_NAME, "Shutting down SessionManager");
if let Some(bus_owner_id) = self.bus_owner_id.take() {
g_debug!(APPLICATION_NAME, "Releasing acquired D-Bus name");
gio::bus_unown_name(bus_owner_id);
}
if let Some((connection, registration_id)) = self.bus_registration.take() {
g_debug!(APPLICATION_NAME, "Unregistering D-Bus object");
connection
.unregister_object(registration_id)
.unwrap_or_else(|e| {
g_critical!(
APPLICATION_NAME,
"Unable to unregister object from D-Bus connection: {}",
e
)
});
}
g_debug!(APPLICATION_NAME, "Removing signal handlers");
for signal_id in self.signals.drain(..) {
signal_id.remove();
}
}
}