Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.cargo
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -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"
|
||||
28
references/daemon.conf
Normal file
28
references/daemon.conf
Normal file
@@ -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"
|
||||
10
resources/introspection.xml
Normal file
10
resources/introspection.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<node name = "/dev/iohub/diya">
|
||||
<interface name='dev.iohub.diya.SessionManager'>
|
||||
<method name='login'>
|
||||
<!--annotation name='org.gtk.GDBus.Annotation' value='OnMethod' /-->
|
||||
<arg type='s' name='user' direction='in' />
|
||||
<arg type='s' name='password' direction='in' />
|
||||
<arg type='b' name='result' direction='out' />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
121
src/configuration.rs
Normal file
121
src/configuration.rs
Normal file
@@ -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<dyn std::error::Error>)` if there was an error reading the file or
|
||||
/// parsing the TOML content.
|
||||
pub fn from(file: &PathBuf) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
g_info!(
|
||||
APPLICATION_NAME,
|
||||
"Loading configuration from: {}",
|
||||
file.display()
|
||||
);
|
||||
let content = std::fs::read_to_string(file)?;
|
||||
let content = content.parse::<Value>()?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
27
src/lib.rs
Normal file
27
src/lib.rs
Normal file
@@ -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;
|
||||
36
src/main.rs
Normal file
36
src/main.rs
Normal file
@@ -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 <dany@iohub.dev>")
|
||||
.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::<String>("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) {}
|
||||
}
|
||||
272
src/session.rs
Normal file
272
src/session.rs
Normal file
@@ -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<Mutex<SessionManagerState>> = Arc::new(Mutex::<SessionManagerState>::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<OwnerId>,
|
||||
signals: Vec<glib::SourceId>,
|
||||
}
|
||||
|
||||
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<DBusConnection>, 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<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(),
|
||||
});
|
||||
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<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 = 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<DBusConnection>, 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<Mutex<SessionManagerState>> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/tests.rs
Normal file
22
src/tests.rs
Normal file
@@ -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"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user