diff --git a/Cargo.lock b/Cargo.lock index 21ee53f..f31bb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,10 +1028,10 @@ dependencies = [ name = "libsession-sys" version = "0.1.0" dependencies = [ - "hyprwire", - "hyprwire-protocols-sessiond", "libc", "libsystemd-sys", + "sessiond-state", + "toml", "zbus", ] @@ -1547,6 +1547,7 @@ dependencies = [ "owo-colors", "serde_json", "termtree", + "uzers", ] [[package]] @@ -1564,6 +1565,7 @@ dependencies = [ "log", "rustix", "serde", + "sessiond-state", "syslog", "toml", "uzers", @@ -1659,6 +1661,15 @@ dependencies = [ "sessiond-power-client", ] +[[package]] +name = "sessiond-state" +version = "0.1.0" +dependencies = [ + "serde", + "thiserror", + "toml", +] + [[package]] name = "sessiond-suspendctl" version = "0.1.0" @@ -1798,18 +1809,18 @@ checksum = "d4d1330fe7f7f872cd05165130b10602d667b205fd85be09be2814b115d4ced9" [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0b03da4..0473219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "sessionctl", "sessiond", + "sessiond-state", "libsession-sys", "libsession-rs", "pam", diff --git a/hyprwire-protocols-sessiond/protocols/session_core_v1.xml b/hyprwire-protocols-sessiond/protocols/session_core_v1.xml index c2b6ea3..00244d3 100644 --- a/hyprwire-protocols-sessiond/protocols/session_core_v1.xml +++ b/hyprwire-protocols-sessiond/protocols/session_core_v1.xml @@ -116,9 +116,6 @@ - - - @@ -196,9 +193,6 @@ - - - @@ -234,9 +228,6 @@ - - - diff --git a/libsession-sys/Cargo.toml b/libsession-sys/Cargo.toml index 8d902e8..dfe5d4d 100644 --- a/libsession-sys/Cargo.toml +++ b/libsession-sys/Cargo.toml @@ -14,17 +14,15 @@ name = "session" crate-type = ["rlib", "cdylib", "staticlib"] [dependencies] -hyprwire = { version = "0.5.1", optional = true } zbus = { version = "5.16.0", optional = true } libsystemd-sys = { version = "0.9.4", optional = true } -hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0", optional = true } libc = "0.2.189" - -[build-dependencies] +toml = { version = "1.1.2", optional = true } +sessiond-state = { path = "../sessiond-state", version = "0.1.0", optional = true } [features] default = ["sessiond", "logind", "consolekit"] -sessiond = ["dep:hyprwire", "dep:hyprwire-protocols-sessiond"] +sessiond = ["dep:sessiond-state"] consolekit = ["dep:zbus"] logind = ["dep:libsystemd-sys"] diff --git a/libsession-sys/src/backend/sessiond.rs b/libsession-sys/src/backend/sessiond.rs index de1e381..eef34f4 100644 --- a/libsession-sys/src/backend/sessiond.rs +++ b/libsession-sys/src/backend/sessiond.rs @@ -1,350 +1,95 @@ -use hyprwire::client; -use hyprwire_protocols_sessiond::client::session_core::v1::session_core_v1; -use hyprwire_protocols_sessiond::client::session_core::v1::session_core_v1::{ - session_core_manager, session_core_session, session_core_user, -}; -use std::ffi; - -pub struct Session { - id: u32, - pid: u32, - uid: u32, - class: session_core_v1::SessionClass, - state: session_core_v1::ActiveState, - timestamp: u32, - done: bool, - is_local: bool, - object: session_core_session::SessionCoreSession, -} - -impl Session { - pub const fn new( - object: session_core_session::SessionCoreSession, - id: u32, - pid: u32, - uid: u32, - ) -> Self { - Self { - id, - pid, - uid, - class: session_core_v1::SessionClass::User, - state: session_core_v1::ActiveState::Online, - timestamp: 0, - done: false, - is_local: false, - object, - } - } -} - -pub struct User { - uid: u32, - state: session_core_v1::UserState, - object: session_core_user::SessionCoreUser, - done: bool, -} +use super::inotify; +use std::os::unix::ffi::OsStrExt; +use std::{env, ffi, fs, path}; pub struct Sessiond { - eq: hyprwire::EventQueue, - session_manager: Option, - sessions: Vec, - users: Vec, -} - -impl hyprwire::Dispatch for Sessiond { - fn event( - &mut self, - object: &session_core_session::SessionCoreSession, - event: ::Event<'_>, - ) { - let Some(index) = self - .sessions - .iter() - .position(|session| session.object == *object) - else { - return; - }; - - match event { - session_core_session::Event::SessionId { session_id } => { - self.sessions[index].id = session_id; - } - session_core_session::Event::Uid { uid } => { - self.sessions[index].uid = uid; - } - session_core_session::Event::Class { class } => { - self.sessions[index].class = class; - } - session_core_session::Event::Active { state } => { - self.sessions[index].state = state; - } - session_core_session::Event::Timestamp { timestamp } => { - self.sessions[index].timestamp = timestamp; - } - session_core_session::Event::Done => { - self.sessions[index].done = true; - } - session_core_session::Event::IsLocal { is_local } => { - self.sessions[index].is_local = is_local != 0; - } - session_core_session::Event::Destroyed => { - self.sessions.remove(index); - } - _ => {} - } - } -} - -impl hyprwire::Dispatch for Sessiond { - fn event( - &mut self, - object: &session_core_user::SessionCoreUser, - event: ::Event<'_>, - ) { - let Some(index) = self.users.iter().position(|user| user.object == *object) else { - return; - }; - - match event { - session_core_user::Event::Uid { uid } => self.users[index].uid = uid, - session_core_user::Event::State { state } => self.users[index].state = state, - session_core_user::Event::Done => self.users[index].done = true, - session_core_user::Event::NotFound => { - self.users.retain(|candidate| candidate.object != *object); - } - _ => {} - } - } + state_path: path::PathBuf, } impl Sessiond { - fn wait_user(&mut self, object: &session_core_user::SessionCoreUser) -> Option { - let eq = self.eq.clone(); - eq.dispatch_events(self, false).ok()?; - loop { - let index = self.users.iter().position(|user| user.object == *object)?; - if self.users[index].done { - return Some(index); - } - eq.dispatch_events(self, true).ok()?; - } - } - - fn wait_session(&mut self, object: &session_core_session::SessionCoreSession) -> Option { - let eq = self.eq.clone(); - eq.dispatch_events(self, false).ok()?; - loop { - let index = self.sessions.iter().position(|s| s.object == *object)?; - if self.sessions[index].done { - return Some(index); - } - eq.dispatch_events(self, true).ok()?; - } - } - pub fn try_connect() -> Option { - let Ok(mut socket) = client::Client::connect("/run/sessiond/sessiond.sock") else { - return None; + let runtime_dir = env::var_os("SESSIOND_RUNTIME_DIR") + .map_or_else(|| path::PathBuf::from("/run/sessiond"), path::PathBuf::from); + let backend = Self { + state_path: runtime_dir.join("state.toml"), }; - let eq = socket.new_event_queue(); - - socket.add_implementation::(); - - let mut sessiond = Self { - eq: eq.clone(), - sessions: Vec::new(), - session_manager: None, - users: Vec::new(), - }; - - eq.wait_for_handshake(&mut sessiond).ok()?; - - // if we fail to bind manager the connectionn is useless, try another backend - sessiond.session_manager = Some(socket.bind(&eq, &mut sessiond, 3).ok()?); + sessiond_state::try_read(&backend.state_path).ok()?; - eq.roundtrip(&mut sessiond).ok()?; - - Some(sessiond) + Some(backend) } } impl super::Backend for Sessiond { fn get_session_id(&mut self, pid: u32) -> Option { - if let Some(object) = self - .sessions - .iter() - .find(|session| session.pid == pid) - .map(|session| session.object.clone()) - { - let index = self.wait_session(&object)?; - return Some(self.sessions[index].id.to_string()); - } - let object = self - .session_manager - .as_ref()? - .send_get_session_by_pid::(pid)?; - self.sessions.push(Session::new(object.clone(), 0, pid, 0)); - self.wait_session(&object) - .map(|index| self.sessions[index].id.to_string()) + let cgroup = fs::read_to_string(format!("/proc/{pid}/cgroup")).ok()?; + cgroup.lines().find_map(|line| { + let path = line.splitn(3, ':').nth(2)?; + path.split('/') + .find_map(|part| part.strip_prefix("session-")?.parse().ok()) + }) } fn get_owner_uid(&mut self, pid: u32) -> Option { - if let Some(object) = self - .sessions - .iter() - .find(|session| session.pid == pid) - .map(|session| session.object.clone()) - { - let index = self.wait_session(&object)?; - return Some(self.sessions[index].uid); - } - let object = self - .session_manager - .as_ref()? - .send_get_session_by_pid::(pid)?; - self.sessions.push(Session::new(object.clone(), 0, pid, 0)); - self.wait_session(&object) - .map(|index| self.sessions[index].uid) + let owner_uid = fs::read_to_string(format!("/proc/{pid}/loginuid")).ok()?; + owner_uid.trim().parse().ok() } fn get_uid(&mut self, session_id: &ffi::CStr) -> Option { - let Ok(session_id) = session_id.to_string_lossy().parse::() else { - return None; - }; - - if let Some(object) = self + let state = sessiond_state::try_read(&self.state_path).ok()?; + let session = state .sessions - .iter() - .find(|session| session.id == session_id) - .map(|session| session.object.clone()) - { - let index = self.wait_session(&object)?; - return Some(self.sessions[index].uid); - } - let object = self - .session_manager - .as_ref()? - .send_get_session::(session_id)?; - self.sessions - .push(Session::new(object.clone(), session_id, 0, 0)); - self.wait_session(&object) - .map(|index| self.sessions[index].uid) + .get(&session_id.to_string_lossy().to_string())?; + + Some(session.uid) } fn get_state(&mut self, uid: u32) -> Option { - let object = if let Some(object) = self - .users - .iter() - .find(|user| user.uid == uid) - .map(|user| user.object.clone()) - { - object - } else { - let object = self.session_manager.as_ref()?.send_get_user::(uid)?; - self.users.push(User { - uid, - state: session_core_v1::UserState::Offline, - object: object.clone(), - done: false, - }); - object - }; - let index = self.wait_user(&object)?; - Some(match self.users[index].state { - session_core_v1::UserState::Offline => crate::LibsessionUserState::Offline, - session_core_v1::UserState::Lingering => crate::LibsessionUserState::Lingering, - session_core_v1::UserState::Online => crate::LibsessionUserState::Online, - session_core_v1::UserState::Active => crate::LibsessionUserState::Active, - session_core_v1::UserState::Closing => crate::LibsessionUserState::Closing, - _ => return None, - }) + let state = sessiond_state::try_read(&self.state_path).ok()?; + let user = state.users.get(&uid)?; + + match user.state { + sessiond_state::UserState::Offline => Some(crate::LibsessionUserState::Offline), + sessiond_state::UserState::Lingering => Some(crate::LibsessionUserState::Lingering), + sessiond_state::UserState::Online => Some(crate::LibsessionUserState::Online), + sessiond_state::UserState::Active => Some(crate::LibsessionUserState::Active), + sessiond_state::UserState::Closing => Some(crate::LibsessionUserState::Closing), + sessiond_state::UserState::Unknown => Some(crate::LibsessionUserState::Unknown), + } } fn get_display(&mut self, uid: u32) -> Option { - // Only the daemon has the complete session list needed to find the display session - let object = self - .session_manager - .as_ref()? - .send_get_display_session_by_uid::(uid)?; - self.sessions.push(Session::new(object.clone(), 0, 0, uid)); - let result = self - .wait_session(&object) - .map(|index| self.sessions[index].id.to_string()); - self.sessions.retain(|session| session.object != object); - drop(object); - result + todo!() } fn get_session_state( &mut self, session_id: &ffi::CStr, ) -> Option { - let Ok(session_id) = session_id.to_string_lossy().parse::() else { - return None; - }; - - if let Some(object) = self - .sessions - .iter() - .find(|session| session.id == session_id) - .map(|session| session.object.clone()) - { - let index = self.wait_session(&object)?; - return Some(match self.sessions[index].state { - session_core_v1::ActiveState::Online => crate::LibsessionSessionState::Online, - session_core_v1::ActiveState::Active => crate::LibsessionSessionState::Active, - session_core_v1::ActiveState::Closing => crate::LibsessionSessionState::Closing, - session_core_v1::ActiveState::Opening => crate::LibsessionSessionState::Opening, - _ => crate::LibsessionSessionState::Unkown, - }); + let state = sessiond_state::try_read(&self.state_path).ok()?; + let session = state.sessions.get(session_id.to_str().ok()?)?; + + match session.active { + sessiond_state::ActiveState::Online => Some(crate::LibsessionSessionState::Online), + sessiond_state::ActiveState::Closing => Some(crate::LibsessionSessionState::Closing), + sessiond_state::ActiveState::Opening => Some(crate::LibsessionSessionState::Opening), + sessiond_state::ActiveState::Active => Some(crate::LibsessionSessionState::Active), + sessiond_state::ActiveState::Unknown => Some(crate::LibsessionSessionState::Unkown), } - let object = self - .session_manager - .as_ref()? - .send_get_session::(session_id)?; - self.sessions - .push(Session::new(object.clone(), session_id, 0, 0)); - self.wait_session(&object) - .map(|index| match self.sessions[index].state { - session_core_v1::ActiveState::Online => crate::LibsessionSessionState::Online, - session_core_v1::ActiveState::Active => crate::LibsessionSessionState::Active, - session_core_v1::ActiveState::Closing => crate::LibsessionSessionState::Closing, - session_core_v1::ActiveState::Opening => crate::LibsessionSessionState::Opening, - _ => crate::LibsessionSessionState::Unkown, - }) } fn is_local_session(&mut self, session_id: &ffi::CStr) -> Option { - let Ok(session_id) = session_id.to_string_lossy().parse::() else { - return None; - }; + let state = sessiond_state::try_read(&self.state_path).ok()?; + let session = state.sessions.get(session_id.to_str().ok()?)?; - if let Some(object) = self - .sessions - .iter() - .find(|session| session.id == session_id) - .map(|session| session.object.clone()) - { - let index = self.wait_session(&object)?; - return Some(self.sessions[index].is_local); - } - let object = self - .session_manager - .as_ref()? - .send_get_session::(session_id)?; - self.sessions - .push(Session::new(object.clone(), session_id, 0, 0)); - self.wait_session(&object) - .map(|index| self.sessions[index].is_local) + Some(session.remote_host.is_some()) } fn get_monitor(&mut self) -> Option> { - Some(Box::new(super::inotify::InotifyMonitor::new( - c"/run/sessiond", + Some(Box::new(inotify::InotifyMonitor::new( + ffi::CString::new(self.state_path.as_os_str().as_bytes()) + .ok()? + .as_c_str(), )?)) } } - -hyprwire::delegate_noop!(Sessiond: ignore session_core_manager::SessionCoreManager); diff --git a/libsession-sys/src/lib.rs b/libsession-sys/src/lib.rs index cc44d7b..55fde63 100644 --- a/libsession-sys/src/lib.rs +++ b/libsession-sys/src/lib.rs @@ -37,6 +37,7 @@ pub enum LibsessionUserState { Online = 2, Active = 3, Closing = 4, + Unknown = 5, } #[repr(C)] diff --git a/sessionctl/Cargo.toml b/sessionctl/Cargo.toml index 57ecf31..44d61e7 100644 --- a/sessionctl/Cargo.toml +++ b/sessionctl/Cargo.toml @@ -22,6 +22,7 @@ comfy-table = "7.2.2" owo-colors = "4.3.0" serde_json = "1.0.145" termtree = "1.0.0" +uzers = "0.12.2" hyprwire = "0.5.1" hyprwire-protocols-sessiond = { path = "../hyprwire-protocols-sessiond", version = "0.2.0" } diff --git a/sessionctl/src/main.rs b/sessionctl/src/main.rs index a171192..9c9f45d 100644 --- a/sessionctl/src/main.rs +++ b/sessionctl/src/main.rs @@ -21,6 +21,12 @@ use seat_core_v1::{seat_core_client, seat_core_device, seat_core_manager, seat_c use serde_json::json; use std::{collections::BTreeSet, env, ffi::OsString, fs, io, path, process, time}; +fn username(uid: u32) -> String { + uzers::get_user_by_uid(uid) + .map(|user| user.name().to_string_lossy().into_owned()) + .unwrap_or_else(|| uid.to_string()) +} + struct Session { object: Option, id: u32, @@ -516,8 +522,10 @@ impl hyprwire::Dispatch for State { }; match event { session_core_session::Event::SessionId { session_id } => s.id = session_id, - session_core_session::Event::Uid { uid } => s.uid = uid, - session_core_session::Event::Username { username } => s.username = username, + session_core_session::Event::Uid { uid } => { + s.uid = uid; + s.username = username(uid); + } session_core_session::Event::Class { class } => s.class = class, session_core_session::Event::Vt { vt } => s.vt = vt, session_core_session::Event::Leader { leader } => s.leader = leader, @@ -545,8 +553,10 @@ impl hyprwire::Dispatch for State { return; }; match event { - session_core_user::Event::Uid { uid } => u.uid = uid, - session_core_user::Event::Username { username } => u.username = username, + session_core_user::Event::Uid { uid } => { + u.uid = uid; + u.username = username(uid); + } session_core_user::Event::Lingering { lingering } => u.lingering = lingering != 0, session_core_user::Event::State { state } => u.state = state, session_core_user::Event::CanModify { can_modify } => u.can_modify = can_modify != 0, @@ -575,9 +585,9 @@ impl hyprwire::Dispatch for State self.inhibitors[index].id = inhibitor_id; } session_core_inhibitor::Event::Who { who } => self.inhibitors[index].who = who, - session_core_inhibitor::Event::Uid { uid } => self.inhibitors[index].uid = uid, - session_core_inhibitor::Event::Username { username } => { - self.inhibitors[index].username = username; + session_core_inhibitor::Event::Uid { uid } => { + self.inhibitors[index].uid = uid; + self.inhibitors[index].username = username(uid); } session_core_inhibitor::Event::Pid { pid } => self.inhibitors[index].pid = pid, session_core_inhibitor::Event::Comm { comm } => self.inhibitors[index].comm = comm, diff --git a/sessiond-state/Cargo.toml b/sessiond-state/Cargo.toml new file mode 100644 index 0000000..9bd28c1 --- /dev/null +++ b/sessiond-state/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sessiond-state" +version = "0.1.0" +edition.workspace = true +license = "MIT" +repository.workspace = true +description = "Shared sessiond state file types" + +[dependencies] +serde = { version = "1.0.228", features = ["derive", "rc"] } +thiserror = "2.0.21" +toml = "1.1.2" + +[lints] +workspace = true diff --git a/sessiond-state/src/lib.rs b/sessiond-state/src/lib.rs new file mode 100644 index 0000000..629d082 --- /dev/null +++ b/sessiond-state/src/lib.rs @@ -0,0 +1,105 @@ +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::{fs, io, path, result, sync}; +use toml::{de, ser}; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum SessionClass { + #[default] + User, + Greeter, + LockScreen, + Background, + BackgroundLight, + Manager, + ManagerEarly, + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ActiveState { + #[default] + Online, + Active, + Closing, + Opening, + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum UserState { + Offline, + Lingering, + Online, + Active, + Closing, + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct User { + pub state: UserState, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct State { + pub next_session_id: u32, + #[serde(default)] + pub users: BTreeMap, + #[serde(default)] + pub sessions: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct Session { + pub uid: u32, + #[serde(default)] + pub class: SessionClass, + #[serde(default)] + pub vt: Option, + pub leader: i32, + #[serde(default)] + pub remote_host: Option>, + pub timestamp: u128, + #[serde(default)] + pub env: BTreeMap, sync::Arc>, + #[serde(default)] + pub active: ActiveState, + pub recoverable: bool, +} + +pub type Result = result::Result; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error("state file I/O failed: {0}")] + Io(#[from] io::Error), + + #[error("invalid state file: {0}")] + Decode(#[from] de::Error), + + #[error("failed to serialize state: {0}")] + Encode(#[from] ser::Error), +} + +/// Reads session state from a TOML file. +/// +/// # Errors +/// +/// Returns an error if the file cannot be read as UTF-8 or its contents +/// cannot be deserialized into [`State`]. +pub fn try_read(path: &path::Path) -> Result { + let state = fs::read_to_string(path)?; + + Ok(toml::from_str::(&state)?) +} diff --git a/sessiond/Cargo.toml b/sessiond/Cargo.toml index 84944a9..04be756 100644 --- a/sessiond/Cargo.toml +++ b/sessiond/Cargo.toml @@ -14,6 +14,7 @@ readme.workspace = true workspace = true [dependencies] +sessiond-state = { path = "../sessiond-state", version = "0.1.0" } anyhow = "1.0.102" clap = { version = "4.6.1", features = ["derive"] } env_logger = { version = "0.11.10", features = ["kv"] } diff --git a/sessiond/src/cgroup.rs b/sessiond/src/cgroup.rs index b40caa8..0ba2aa8 100644 --- a/sessiond/src/cgroup.rs +++ b/sessiond/src/cgroup.rs @@ -269,8 +269,6 @@ pub fn handle_cgroup_empty( Entity, &session::SessionId, &session::Uid, - &session::Gid, - &session::Username, &session::Objects, ), With, @@ -292,14 +290,13 @@ pub fn handle_cgroup_empty( .get(&empty.uid.0) .is_some_and(|&entity| lingering_users.contains(entity)); - if let Some((entity, session_id, uid, gid, username, objects)) = sessions + if let Some((entity, session_id, uid, objects)) = sessions .iter() - .find(|(_, id, _, _, _, _)| **id == empty.session_id) + .find(|(_, id, _, _)| **id == empty.session_id) { log::info!( session_id = session_id.as_raw(), - uid = uid.0, - username = username.0; + uid = uid.0; "session destroyed" ); @@ -325,7 +322,7 @@ pub fn handle_cgroup_empty( .users .get(&sync::Arc::from(user.name().to_string_lossy())) .unwrap_or(&default_config) - .runtime_dir(uid.0, gid.0), + .runtime_dir(uid.0), ) { log::error!(uid = uid.0, error = e.to_string().as_str(); "failed to remove runtime dir"); diff --git a/sessiond/src/config.rs b/sessiond/src/config.rs index 7489c21..9ba39fe 100644 --- a/sessiond/src/config.rs +++ b/sessiond/src/config.rs @@ -23,24 +23,22 @@ impl Default for UserConfig { } impl UserConfig { - pub fn runtime_dir(&self, uid: u32, gid: u32) -> sync::Arc { + pub fn runtime_dir(&self, uid: u32) -> sync::Arc { self.runtime_dir .replace(concat!("{", "uid", "}"), &uid.to_string()) - .replace(concat!("{", "gid", "}"), &gid.to_string()) .into() } pub fn environment( &self, uid: u32, - gid: u32, session_id: u32, term: Option>, sessiond_runtime_dir: sync::Arc, ) -> BTreeMap, sync::Arc> { #[cfg(feature = "consolekit")] let _ = session_id; - let runtime_dir = self.runtime_dir(uid, gid); + let runtime_dir = self.runtime_dir(uid); let mut env: BTreeMap, sync::Arc> = BTreeMap::from([ (sync::Arc::from("XDG_RUNTIME_DIR"), runtime_dir), #[cfg(not(feature = "consolekit"))] diff --git a/sessiond/src/inhibit.rs b/sessiond/src/inhibit.rs index 73a28f2..6a7a045 100644 --- a/sessiond/src/inhibit.rs +++ b/sessiond/src/inhibit.rs @@ -79,9 +79,6 @@ pub struct What(pub(crate) u32); #[derive(Component, Debug, Clone, Copy)] pub struct Uid(pub(crate) u32); -#[derive(Component, Debug)] -pub struct Username(pub(crate) String); - #[derive(Component, Debug, Clone, Copy)] pub struct Pid(pub(crate) u32); @@ -99,7 +96,6 @@ pub struct InhibitorBundle { pub who: Who, pub why: Why, pub uid: Uid, - pub username: Username, pub pid: Pid, pub comm: Comm, pub mode: Mode, @@ -177,9 +173,6 @@ impl hyprwire::Dispatch for crat let creds = *object.client().creds(); let uid = creds.uid.as_raw(); let pid = creds.pid.as_raw_pid().cast_unsigned(); - let username = uzers::get_user_by_uid(uid) - .map(|user| user.name().to_string_lossy().into_owned()) - .unwrap_or_default(); let comm = fs::read_to_string(format!("/proc/{pid}/comm")) .map(|comm| comm.trim().to_owned()) .unwrap_or_default(); @@ -192,7 +185,6 @@ impl hyprwire::Dispatch for crat who: Who(who), why: Why(why), uid: Uid(uid), - username: Username(username), pid: Pid(pid), comm: Comm(comm), mode: Mode(mode), @@ -292,7 +284,6 @@ pub fn handle_add_object( &Who, &Why, &Uid, - &Username, &Pid, &Comm, &Mode, @@ -302,9 +293,9 @@ pub fn handle_add_object( >, ) { for request in requests.read() { - let Some((id, what, who, why, uid, username, pid, comm, mode, mut objects)) = inhibitors + let Some((id, what, who, why, uid, pid, comm, mode, mut objects)) = inhibitors .iter_mut() - .find(|(id, _, _, _, _, _, _, _, _, _)| **id == request.inhibitor_id) + .find(|(id, _, _, _, _, _, _, _, _)| **id == request.inhibitor_id) else { request.object.send_active(0); request.object.send_destroyed(); @@ -314,7 +305,6 @@ pub fn handle_add_object( request.object.send_inhibitor_id(id.as_raw()); request.object.send_who(&who.0); request.object.send_uid(uid.0); - request.object.send_username(&username.0); request.object.send_pid(pid.0); request.object.send_comm(&comm.0); request.object.send_what(what.0); diff --git a/sessiond/src/session.rs b/sessiond/src/session.rs index d427db4..e9d1627 100644 --- a/sessiond/src/session.rs +++ b/sessiond/src/session.rs @@ -92,7 +92,6 @@ impl SessionManagementTarget { pub struct PendingSession { pub manager: Option, pub uid: u32, - pub gid: u32, pub class: Option, pub vt: Option, pub remote_host: Option>, @@ -153,8 +152,6 @@ pub struct SessionBundle { pub id: SessionId, pub class: SessionClass, pub uid: Uid, - pub gid: Gid, - pub username: Username, pub timestamp: Timestamp, pub leader_id: LeaderId, pub env: SessionEnv, @@ -177,12 +174,6 @@ pub struct IsLocal; #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct Uid(pub u32); -#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] -pub struct Gid(pub u32); - -#[derive(Component, Debug, Clone, PartialEq, Eq)] -pub struct Username(pub sync::Arc); - #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct Vt(pub num::NonZeroU32); @@ -271,8 +262,6 @@ pub fn spawn( session_id: SessionId, class: SessionClass, uid: u32, - gid: u32, - username: sync::Arc, vt: Option, leader_id: i32, remote_host: Option>, @@ -288,8 +277,6 @@ pub fn spawn( id: session_id, class, uid: Uid(uid), - gid: Gid(gid), - username: Username(username), timestamp: Timestamp(time::SystemTime::now()), leader_id: LeaderId(leader_id), env: SessionEnv(env), @@ -535,7 +522,6 @@ pub fn handle_add_object( Entity, &SessionId, &Uid, - &Username, Option<&Vt>, &SessionClass, &ActiveState, @@ -554,7 +540,6 @@ pub fn handle_add_object( entity, session_id, uid, - username, vt, class, state, @@ -565,9 +550,7 @@ pub fn handle_add_object( mut objects, )) = sessions .iter_mut() - .find(|(_, session_id, _, _, _, _, _, _, _, _, _, _)| { - **session_id == request.session_id - }) + .find(|(_, session_id, _, _, _, _, _, _, _, _, _)| **session_id == request.session_id) else { log::warn!(session_id = request.session_id.as_raw(); "get_session_by_pid requested for unknown session"); request.object.send_destroyed(); @@ -577,7 +560,6 @@ pub fn handle_add_object( log::debug!(session_id = request.session_id.as_raw(), uid = request.object.client().creds().uid.as_raw(); "session info requested"); request.object.send_session_id(session_id.as_raw()); request.object.send_uid(uid.0); - request.object.send_username(&username.0); request.object.send_class((*class).into()); request.object.send_vt(vt.map_or(0, |vt| vt.0.get())); request.object.send_active(state.0); @@ -801,21 +783,18 @@ pub fn handle_session_management_action( uid, leader_id, } => { - if let Some(user) = uzers::get_user_by_uid(*uid) { - pending_sessions.by_management_object.insert( - object.clone(), - PendingSession { - manager: management_object.clone(), - uid: *uid, - gid: user.primary_group_id(), - class: None, - vt: None, - remote_host: None, - env: BTreeMap::new(), - leader_id: *leader_id, - }, - ); - } + pending_sessions.by_management_object.insert( + object.clone(), + PendingSession { + manager: management_object.clone(), + uid: *uid, + class: None, + vt: None, + remote_host: None, + env: BTreeMap::new(), + leader_id: *leader_id, + }, + ); continue; } SessionManagementAction::Env { key, value } => { @@ -858,7 +837,7 @@ pub fn handle_session_management_action( ); return; }; - let username = user.name().to_string_lossy().into(); + let username: sync::Arc = user.name().to_string_lossy().into(); let session_id = next_id.take(); @@ -894,7 +873,7 @@ pub fn handle_session_management_action( let user_config = config.users.get(&username).cloned().unwrap_or_default(); user::create_runtime_dir( pending_session.uid, - &user_config.runtime_dir(pending_session.uid, pending_session.gid), + &user_config.runtime_dir(pending_session.uid), ); let is_local = pending_session.remote_host.is_none(); @@ -910,7 +889,6 @@ pub fn handle_session_management_action( let term = pending_session.env.get("TERM").cloned(); let mut env = user_config.environment( pending_session.uid, - pending_session.gid, session_id.as_raw(), term, sync::Arc::clone(&sessiond_runtime_dir.0), @@ -929,8 +907,6 @@ pub fn handle_session_management_action( session_id, pending_session.class.unwrap_or_default(), pending_session.uid, - pending_session.gid, - username, pending_session.vt.map(num::NonZeroU32::get), pending_session.leader_id, pending_session.remote_host, diff --git a/sessiond/src/state.rs b/sessiond/src/state.rs index 9074d4a..385b47d 100644 --- a/sessiond/src/state.rs +++ b/sessiond/src/state.rs @@ -1,66 +1,41 @@ use crate::{cgroup, config, session, user}; -use anyhow::Context; use bevy_ecs::prelude::*; use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; +use sessiond_state::{ActiveState, Session, SessionClass, State, UserState}; use std::io::Write; use std::os::unix::fs::PermissionsExt; use std::{fs, path, sync, time}; -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct State { - pub next_session_id: u32, - #[serde(default)] - pub sessions: BTreeMap, +trait SessionExt { + fn class(&self) -> session::SessionClass; + fn timestamp(&self) -> time::SystemTime; + fn active_state(&self) -> session_core_v1::ActiveState; } -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub struct Session { - pub uid: u32, - pub gid: u32, - pub username: sync::Arc, - #[serde(default)] - pub class: String, - #[serde(default)] - pub vt: Option, - pub leader: i32, - #[serde(default)] - pub remote_host: Option>, - pub timestamp: u128, - #[serde(default)] - pub env: BTreeMap, sync::Arc>, - #[serde(default)] - pub active: String, - pub recoverable: bool, -} - -impl Session { - pub fn class(&self) -> session::SessionClass { - match self.class.as_str() { - "greeter" => session::SessionClass::Greeter, - "lock-screen" => session::SessionClass::LockScreen, - "background" => session::SessionClass::Background, - "background-light" => session::SessionClass::BackgroundLight, - "manager" => session::SessionClass::Manager, - "manager-early" => session::SessionClass::ManagerEarly, - _ => session::SessionClass::User, +impl SessionExt for Session { + fn class(&self) -> session::SessionClass { + match self.class { + SessionClass::Greeter => session::SessionClass::Greeter, + SessionClass::LockScreen => session::SessionClass::LockScreen, + SessionClass::Background => session::SessionClass::Background, + SessionClass::BackgroundLight => session::SessionClass::BackgroundLight, + SessionClass::Manager => session::SessionClass::Manager, + SessionClass::ManagerEarly => session::SessionClass::ManagerEarly, + SessionClass::User | SessionClass::Unknown => session::SessionClass::User, } } - pub fn timestamp(&self) -> time::SystemTime { + fn timestamp(&self) -> time::SystemTime { time::UNIX_EPOCH + time::Duration::from_micros(u64::try_from(self.timestamp).unwrap_or(u64::MAX)) } - pub fn active_state(&self) -> session_core_v1::ActiveState { - match self.active.as_str() { - "active" => session_core_v1::ActiveState::Active, - "closing" => session_core_v1::ActiveState::Closing, - "opening" => session_core_v1::ActiveState::Opening, - _ => session_core_v1::ActiveState::Online, + fn active_state(&self) -> session_core_v1::ActiveState { + match self.active { + ActiveState::Active => session_core_v1::ActiveState::Active, + ActiveState::Closing => session_core_v1::ActiveState::Closing, + ActiveState::Opening => session_core_v1::ActiveState::Opening, + ActiveState::Online | ActiveState::Unknown => session_core_v1::ActiveState::Online, } } } @@ -74,8 +49,6 @@ pub fn write_state( ( &session::SessionId, &session::Uid, - &session::Gid, - &session::Username, &session::SessionClass, Option<&session::Vt>, &session::LeaderId, @@ -94,8 +67,6 @@ pub fn write_state( Or<( Added, Changed, - Changed, - Changed, Changed, Changed, Changed, @@ -108,8 +79,26 @@ pub fn write_state( >, removed: RemovedComponents, next_session_id: Res, + users: Query<(&session::Uid, &user::UserState), With>, + changed_users: Query< + Entity, + ( + With, + Or<( + Added, + Changed, + Changed, + )>, + ), + >, + removed_users: RemovedComponents, ) { - if changed.is_empty() && removed.is_empty() && !next_session_id.is_changed() { + if changed.is_empty() + && removed.is_empty() + && !next_session_id.is_changed() + && changed_users.is_empty() + && removed_users.is_empty() + { return; } @@ -119,8 +108,6 @@ pub fn write_state( |( id, uid, - gid, - username, class, vt, leader, @@ -136,28 +123,26 @@ pub fn write_state( .unwrap_or_default() .as_micros(); let class = match class { - session::SessionClass::User => "user", - session::SessionClass::Greeter => "greeter", - session::SessionClass::LockScreen => "lock-screen", - session::SessionClass::Background => "background", - session::SessionClass::BackgroundLight => "background-light", - session::SessionClass::Manager => "manager", - session::SessionClass::ManagerEarly => "manager-early", + session::SessionClass::User => SessionClass::User, + session::SessionClass::Greeter => SessionClass::Greeter, + session::SessionClass::LockScreen => SessionClass::LockScreen, + session::SessionClass::Background => SessionClass::Background, + session::SessionClass::BackgroundLight => SessionClass::BackgroundLight, + session::SessionClass::Manager => SessionClass::Manager, + session::SessionClass::ManagerEarly => SessionClass::ManagerEarly, }; let active = match active_state.0 { - session_core_v1::ActiveState::Active => "active", - session_core_v1::ActiveState::Closing => "closing", - session_core_v1::ActiveState::Opening => "opening", - _ => "online", + session_core_v1::ActiveState::Active => ActiveState::Active, + session_core_v1::ActiveState::Closing => ActiveState::Closing, + session_core_v1::ActiveState::Opening => ActiveState::Opening, + _ => ActiveState::Online, }; ( id.as_raw().to_string(), Session { uid: uid.0, - gid: gid.0, - username: sync::Arc::clone(&username.0), - class: class.to_owned(), + class, vt: vt.map(|vt| vt.0.get()), leader: leader.0, remote_host: remote_host.map(|host| sync::Arc::clone(&host.0)), @@ -167,7 +152,7 @@ pub fn write_state( .iter() .map(|(name, value)| (sync::Arc::clone(name), sync::Arc::clone(value))) .collect(), - active: active.to_owned(), + active, recoverable: recoverable.is_some(), }, ) @@ -177,6 +162,20 @@ pub fn write_state( let state = State { next_session_id: next_session_id.0.as_raw(), + users: users + .iter() + .map(|(uid, state)| { + let state = match state.0 { + session_core_v1::UserState::Active => UserState::Active, + session_core_v1::UserState::Closing => UserState::Closing, + session_core_v1::UserState::Lingering => UserState::Lingering, + session_core_v1::UserState::Online => UserState::Online, + session_core_v1::UserState::Offline => UserState::Offline, + _ => UserState::Unknown, + }; + (uid.0, sessiond_state::User { state }) + }) + .collect(), sessions, }; @@ -185,23 +184,12 @@ pub fn write_state( } } -impl State { - pub fn load(path: &path::Path) -> anyhow::Result { - let state = fs::read_to_string(path) - .with_context(|| format!("failed to read state file: {}", path.display()))?; - - toml::from_str::(&state) - .inspect_err(|err| { - log::error!( - path = path.to_string_lossy().as_ref(), - error = err.to_string().as_str(); - "failed to parse config file" - ); - }) - .with_context(|| format!("failed to parse staet file {}", path.display())) - } +trait StateExt: Sized { + fn write(&self, path: &path::Path) -> anyhow::Result<()>; +} - pub fn write(&self, path: &path::Path) -> anyhow::Result<()> { +impl StateExt for State { + fn write(&self, path: &path::Path) -> anyhow::Result<()> { let mut temp_path = path.as_os_str().to_os_string(); temp_path.push("~"); @@ -228,7 +216,7 @@ pub fn load_state( loop_handle: NonSend<'_, calloop::LoopHandle<'static, crate::Sessiond>>, ) { if state_writer.0.exists() { - match State::load(&state_writer.0) { + match sessiond_state::try_read(&state_writer.0) { Ok(saved_state) => { next_session_id.0 = session::SessionId::from_raw(saved_state.next_session_id); @@ -243,6 +231,13 @@ pub fn load_state( }; let session_id = session::SessionId::from_raw(raw_id); + let Some(user) = uzers::get_user_by_uid(saved_session.uid) else { + log::warn!(session_id = raw_id, uid = saved_session.uid; "ignoring session whose user no longer exists"); + continue; + }; + let username: sync::Arc = + user.name().to_string_lossy().into_owned().into(); + let cgroup = match cgroup_manager .restore_session_cgroup(saved_session.uid, session_id) { @@ -264,10 +259,10 @@ pub fn load_state( let runtime_dir = config .users - .get(&saved_session.username) + .get(&username) .cloned() .unwrap_or_default() - .runtime_dir(saved_session.uid, saved_session.gid); + .runtime_dir(saved_session.uid); user::create_runtime_dir(saved_session.uid, &runtime_dir); let active_state = saved_session.active_state(); @@ -277,8 +272,6 @@ pub fn load_state( session_id, saved_session.class(), saved_session.uid, - saved_session.gid, - sync::Arc::clone(&saved_session.username), saved_session.vt, saved_session.leader, saved_session.remote_host.as_ref().map(sync::Arc::clone), diff --git a/sessiond/src/user.rs b/sessiond/src/user.rs index 7992b8e..d56168d 100644 --- a/sessiond/src/user.rs +++ b/sessiond/src/user.rs @@ -3,7 +3,7 @@ use bevy_ecs::prelude::*; use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1; use hyprwire_protocols_sessiond::server::session_core::v1::session_core_v1::session_core_user; use std::collections::HashMap; -use std::{fs, io, sync}; +use std::{fs, io}; #[derive(Component, Debug, Clone, Copy, PartialEq, Eq)] pub struct UserState(pub session_core_v1::UserState); @@ -32,7 +32,6 @@ pub struct UserRegistry { pub struct UserBundle { pub marker: User, pub uid: session::Uid, - pub username: session::Username, pub state: UserState, pub objects: Objects, } @@ -49,7 +48,6 @@ pub struct Objects(pub Vec); pub fn spawn( commands: &mut Commands, uid: u32, - username: sync::Arc, lingering: bool, state: UserState, user_registry: &mut ResMut, @@ -58,7 +56,6 @@ pub fn spawn( let mut cmd = commands.spawn(UserBundle { marker: User, uid: session::Uid(uid), - username: session::Username(username), state, objects: Objects::default(), }); @@ -98,13 +95,13 @@ pub fn remove_runtime_dir(path: &str) -> io::Result<()> { pub fn ensure_session_users( mut commands: Commands, sessions: Query< - (&session::Uid, &session::Username, &session::ActiveState), + (&session::Uid, &session::ActiveState), (With, Added), >, mut user_registry: ResMut, info_managers: Res, ) { - for (uid, username, session_state) in sessions { + for (uid, session_state) in sessions { if user_registry.by_uid.contains_key(&uid.0) { continue; } @@ -118,7 +115,6 @@ pub fn ensure_session_users( spawn( &mut commands, uid.0, - sync::Arc::clone(&username.0), false, state, &mut user_registry, @@ -182,7 +178,6 @@ pub fn handle_add_object( ( Entity, &session::Uid, - &session::Username, Option<&Lingering>, &UserState, &mut Objects, @@ -192,9 +187,9 @@ pub fn handle_add_object( mut user_registry: ResMut, ) { for request in requests.read() { - let Some((entity, uid, username, lingering, state, mut objects)) = users + let Some((entity, uid, lingering, state, mut objects)) = users .iter_mut() - .find(|(_, uid, _, _, _, _)| uid.0 == request.uid) + .find(|(_, uid, _, _, _)| uid.0 == request.uid) else { log::warn!(uid = request.uid; "get_user requested for unknown user"); request.object.send_not_found(); @@ -202,7 +197,6 @@ pub fn handle_add_object( }; request.object.send_uid(uid.0); - request.object.send_username(&username.0); if lingering.is_some() { request.object.send_lingering(1); } else { @@ -364,34 +358,26 @@ pub fn initialize_lingering_users( .filter(|(_, config)| config.linger) .filter_map(|(name, config)| { let user = uzers::get_user_by_name(name.as_ref())?; - Some(( - user.uid(), - config.runtime_dir(user.uid(), user.primary_group_id()), - )) + Some((user.uid(), config.runtime_dir(user.uid()))) }) .collect::>(); for (uid, runtime_dir) in lingering_users { - if let Some(user) = uzers::get_user_by_uid(uid) { - spawn( - &mut commands, + spawn( + &mut commands, + uid, + true, + UserState::LINGERING, + &mut user_registry, + &info_managers, + ); + + create_runtime_dir(uid, &runtime_dir); + if let Err(e) = cgroup_manager.ensure_user_cgroup(uid) { + log::error!( uid, - user.name().to_string_lossy().into(), - true, - UserState::LINGERING, - &mut user_registry, - &info_managers, + error = e.to_string().as_str(); + "failed to create user cgroup" ); - - create_runtime_dir(uid, &runtime_dir); - if let Err(e) = cgroup_manager.ensure_user_cgroup(uid) { - log::error!( - uid, - error = e.to_string().as_str(); - "failed to create user cgroup" - ); - } - } else { - log::warn!(uid; "configured lingering user no longer exists"); } } }