diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3e0001d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+.cargo
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..6b46fa5
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "dysm-rs"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = "4"
+gio = "0.22.2"
+glib = "0.21.5"
+lazy_static = "1.5.0"
+libc = "0.2.183"
+toml = "0.8"
diff --git a/references/daemon.conf b/references/daemon.conf
new file mode 100644
index 0000000..b057fe0
--- /dev/null
+++ b/references/daemon.conf
@@ -0,0 +1,28 @@
+# this file is the configuration file for the SessionMaganer daemon
+# daemon
+# By default, the daemon will look for this file in the following locations
+# /etc/diya/daemon.conf
+# or it can be specified using the -c option
+# example: diya-session-manager -c /path/to/daemon.conf
+
+# PAM service
+# the pam service used for authentication
+pam_service = "diya"
+
+# Login session command
+# The command to run to start a login session
+# this command will handle the user input and send user
+# credentials to the daemon via Dbus message
+login_session_command = "/usr/bin/diyac -x /usr/bin/diya-login-shell"
+
+# login session user
+# The user that owns the login session, root by default
+# if this setting is not set
+login_session_user = "xdg"
+
+# User session command
+# The command to run to start a user session after the
+# login session is successful
+# the logged in user will own this session
+
+user_session_command = "/usr/bin/diyac -x /usr/bin/diya-shell"
\ No newline at end of file
diff --git a/resources/introspection.xml b/resources/introspection.xml
new file mode 100644
index 0000000..977b9be
--- /dev/null
+++ b/resources/introspection.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/configuration.rs b/src/configuration.rs
new file mode 100644
index 0000000..2d91a54
--- /dev/null
+++ b/src/configuration.rs
@@ -0,0 +1,121 @@
+//! # Configuration Module
+//!
+//! This module defines the `Configuration` struct, which is responsible for
+//! loading and managing the application's configuration settings from a TOML file.
+//! The configuration includes parameters such as the PAM service name,
+//! commands for login and user sessions, and the user to run login sessions as.
+//! The `Configuration` struct provides methods to access these settings and implements
+//! the `Display` trait for easy debugging and logging.
+use crate::APPLICATION_NAME;
+use core::fmt;
+use glib::g_info;
+use std::path::PathBuf;
+use toml::Value;
+
+/// Configuration
+///
+/// Represents the application's configuration settings loaded from a TOML file.
+pub struct Configuration {
+ /// The name of the PAM service to use (default: "diya").
+ pam_service: String,
+ /// The command to execute for login sessions (required).
+ login_session_command: String,
+ /// The user to run login sessions as (default: "root").
+ login_session_user: String, // optional (default root)
+ /// The command to execute for user sessions (required).
+ user_session_command: String,
+}
+
+impl Configuration {
+ /// Load configuration from a TOML file.
+ ///
+ /// # Arguments
+ ////
+ /// * `file` - A `PathBuf` pointing to the configuration file.
+ ///
+ /// # Returns
+ /// * `Ok(Configuration)` if the configuration was successfully loaded and parsed.
+ /// * `Err(Box)` if there was an error reading the file or
+ /// parsing the TOML content.
+ pub fn from(file: &PathBuf) -> Result> {
+ g_info!(
+ APPLICATION_NAME,
+ "Loading configuration from: {}",
+ file.display()
+ );
+ let content = std::fs::read_to_string(file)?;
+ let content = content.parse::()?;
+ let table = content.as_table().ok_or("Invalid configuration format")?;
+
+ Ok(Configuration {
+ pam_service: table
+ .get("pam_service")
+ .and_then(Value::as_str)
+ .unwrap_or("diya")
+ .to_string(),
+ login_session_command: table
+ .get("login_session_command")
+ .and_then(Value::as_str)
+ .ok_or("Missing login session command")?
+ .to_string(),
+ login_session_user: table
+ .get("login_session_user")
+ .and_then(Value::as_str)
+ .unwrap_or("root")
+ .to_string(),
+ user_session_command: table
+ .get("user_session_command")
+ .and_then(Value::as_str)
+ .ok_or("Missing user session command")?
+ .to_string(),
+ })
+ }
+
+ /// Get the PAM service name.
+ ///
+ /// # Returns
+ /// * A string slice representing the PAM service name.
+ /// Defaults to "diya" if not specified in the configuration.
+ pub fn pam_service(&self) -> &str {
+ &self.pam_service
+ }
+
+ /// Get the login session command.
+ ///
+ /// # Returns
+ /// * A string slice representing the login session command.
+ pub fn login_session_command(&self) -> &str {
+ &self.login_session_command
+ }
+ /// Get the login session user.
+ ///
+ /// # Returns
+ /// * A string slice representing the user to run login sessions as.
+ /// * Defaults to "root" if not specified in the configuration.
+ pub fn login_session_user(&self) -> &str {
+ &self.login_session_user
+ }
+
+ /// Get the user session command.
+ ///
+ /// # Returns
+ /// * A string slice representing the user session command.
+ pub fn user_session_command(&self) -> &str {
+ &self.user_session_command
+ }
+}
+
+impl fmt::Display for Configuration {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ writeln!(f, "Configuration:")?;
+ writeln!(f, " - PAM Service: {}", self.pam_service)?;
+ writeln!(
+ f,
+ " - Login Session Command: {}",
+ self.login_session_command
+ )?;
+ writeln!(f, " - Login Session User: {}", self.login_session_user)?;
+ writeln!(f, " - User Session Command: {}", self.user_session_command)?;
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..c4dd1d1
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,27 @@
+//! # dysm-rs main library
+//!
+//!
+//!
+#![warn(
+ trivial_casts,
+ trivial_numeric_casts,
+ unused_extern_crates,
+ unused_qualifications,
+ unused_results,
+ missing_docs,
+ clippy::pedantic,
+ clippy::missing_docs_in_private_items
+)]
+
+pub mod configuration;
+pub mod session;
+pub use session::SessionManager;
+pub use session::SessionManagerInterface;
+
+/// The version of the dysm-rs library, derived from the Cargo package version.
+pub const VERSION: &str = env!("CARGO_PKG_VERSION");
+/// The application name for logging and identification purposes.
+pub const APPLICATION_NAME: &str = "dysm-rs";
+
+#[cfg(test)]
+pub mod tests;
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..5d000bb
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,36 @@
+use clap::{Arg, Command};
+use dysm_rs::{SessionManager, SessionManagerInterface};
+use std::path::PathBuf;
+
+const DEFAULT_CONFIG_PATH: &str = "/etc/diya/daemon.conf";
+
+fn main() {
+ let matches = Command::new("dysm")
+ .version(env!("CARGO_PKG_VERSION"))
+ .author("Dany LE ")
+ .about("Diya Session Manager")
+ .arg(
+ Arg::new("config")
+ .short('c')
+ .long("config")
+ .num_args(1)
+ .value_name("FILE")
+ .value_hint(clap::ValueHint::FilePath)
+ .help("Path to configuration file"),
+ )
+ .arg(
+ Arg::new("session")
+ .short('s')
+ .long("session")
+ .help("Using session DEBUS instead of system")
+ .action(clap::ArgAction::SetTrue),
+ )
+ .get_matches();
+ let default_config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
+ let config_file = matches
+ .get_one::("config")
+ .map(PathBuf::from)
+ .unwrap_or(default_config_path);
+ let is_dbus_session = matches.get_flag("session");
+ while let true = SessionManager::instance().run(&config_file, is_dbus_session) {}
+}
diff --git a/src/session.rs b/src/session.rs
new file mode 100644
index 0000000..eb033ac
--- /dev/null
+++ b/src/session.rs
@@ -0,0 +1,272 @@
+//! Session management for the display manager.
+//!
+//! This module defines the `SessionManager` struct, which is responsible for
+//! managing user sessions in the display manager. 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, ResourceLookupFlags};
+use glib::{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! {
+ /// static global mutex variable that stores the current state of the session manager process, which can be either Running, End, or Reload.
+ /// This variable is used to control the behavior of the session manager based on signals received (e.g., SIGHUP for reload, SIGINT/SIGTERM for shutdown).
+ static ref G_SESSION_MANAGER: Arc> = Arc::new(Mutex::::new(SessionManagerState::End));
+}
+
+pub enum SessionManagerState {
+ Running(SessionManager),
+ End,
+ 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,
+ // introspection: gio::DBusNodeInfo,
+ bus_owner_id: Option,
+ signals: Vec,
+}
+
+pub trait SessionManagerInterface {
+ fn run(&self, config: &PathBuf, is_dbus_session: bool) -> bool;
+ fn quit(&self, reload: bool);
+}
+
+trait SessionManagerCallback {
+ fn on_name_acquired(&self, connection: DBusConnection, name: &str);
+ fn on_bus_acquired(&self, connection: DBusConnection, name: &str);
+ fn on_name_lost(&self, connection: Option, name: &str);
+ 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,
+ );
+ fn on_process_monitor(&self);
+}
+
+impl SessionManagerInterface for Arc> {
+ 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(),
+ });
+ 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();
+ if let SessionManagerState::Reload = self.lock().as_deref().unwrap() {
+ true
+ } else {
+ 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> {
+ 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 = gio::DBusNodeInfo::for_xml(G_DBUS_INTROSPECTION).unwrap();
+ let interface = &introspection.interfaces()[0];
+ let _ = 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()
+ .expect("Failed to register D-Bus object");
+ }
+
+ fn on_name_lost(&self, _connection: Option, name: &str) {
+ g_warning!(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(¶meters, 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_process_monitor(&self) {}
+}
+
+impl SessionManager {
+ pub fn instance() -> Arc> {
+ G_SESSION_MANAGER.clone()
+ }
+ /// Set up the session manager.
+ ///
+ /// This method starts the main loop and begins handling session events.
+ /// It will block until the main loop is quit, at which point it will return.
+ 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_process_monitor();
+ glib::ControlFlow::Continue
+ });
+ self.signals.push(processes_monitor_id);
+ // Implementation for running the session manager
+ }
+
+ 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");
+ }
+}
+
+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);
+ }
+ for signal_id in self.signals.drain(..) {
+ signal_id.remove();
+ }
+ }
+}
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644
index 0000000..243fb78
--- /dev/null
+++ b/src/tests.rs
@@ -0,0 +1,22 @@
+//! Tests for the dysm-rs library.
+//!
+//! This module contains tests for the `dysm-rs` library
+
+use super::configuration::*;
+
+#[test]
+fn test_configuration_loading() {
+ let config_path = std::path::PathBuf::from("references/daemon.conf");
+ let config = Configuration::from(&config_path).expect("Failed to load configuration");
+
+ assert_eq!(config.pam_service(), "diya");
+ assert_eq!(
+ config.login_session_command(),
+ "/usr/bin/diyac -x /usr/bin/diya-login-shell"
+ );
+ assert_eq!(config.login_session_user(), "xdg");
+ assert_eq!(
+ config.user_session_command(),
+ "/usr/bin/diyac -x /usr/bin/diya-shell"
+ );
+}