Resolve helper programs and session dirs without FHS paths

systemctl was invoked as /usr/bin/systemctl, and session directories were
fixed to /usr/share. Both assume an FHS layout and fail on NixOS.

Programs now resolve via PATH; session directories walk XDG_DATA_DIRS with
/usr/share as fallback. Behaviour on Arch is unchanged.
This commit is contained in:
2026-08-07 13:24:38 +02:00
parent dcea0ba934
commit 95f9071987
2 changed files with 78 additions and 8 deletions
+2 -2
View File
@@ -105,7 +105,7 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
/// agent — the greeter session has none, so without it a denied authorization
/// would hang instead of failing fast.
pub fn reboot() -> Result<(), PowerError> {
run_command("reboot", "/usr/bin/systemctl", &["--no-ask-password", "reboot"])
run_command("reboot", "systemctl", &["--no-ask-password", "reboot"])
}
/// Shut down the system via systemctl.
@@ -113,7 +113,7 @@ pub fn reboot() -> Result<(), PowerError> {
/// `--no-ask-password` for the same reason as [`reboot`] — the agent-less
/// greeter session has nothing to answer an authorization challenge.
pub fn shutdown() -> Result<(), PowerError> {
run_command("shutdown", "/usr/bin/systemctl", &["--no-ask-password", "poweroff"])
run_command("shutdown", "systemctl", &["--no-ask-password", "poweroff"])
}
#[cfg(test)]
+76 -6
View File
@@ -4,8 +4,34 @@
use std::fs;
use std::path::{Path, PathBuf};
const DEFAULT_WAYLAND_DIRS: &[&str] = &["/usr/share/wayland-sessions"];
const DEFAULT_XSESSION_DIRS: &[&str] = &["/usr/share/xsessions"];
/// Fallback data directory used when XDG_DATA_DIRS is unset or does not
/// contain the session directory.
const FALLBACK_DATA_DIR: &str = "/usr/share";
/// Build the search path for a session directory.
///
/// Walks XDG_DATA_DIRS as specified by the XDG Base Directory spec and
/// appends `/usr/share` as a fallback, so setups that place session files
/// outside the FHS layout are found as well.
fn session_search_dirs(subdir: &str) -> Vec<PathBuf> {
session_search_dirs_from(&std::env::var("XDG_DATA_DIRS").unwrap_or_default(), subdir)
}
/// Build the search path from an explicit XDG_DATA_DIRS value.
fn session_search_dirs_from(data_dirs: &str, subdir: &str) -> Vec<PathBuf> {
let mut dirs: Vec<PathBuf> = data_dirs
.split(':')
.filter(|s| !s.is_empty())
.map(|d| PathBuf::from(d).join(subdir))
.collect();
let fallback = PathBuf::from(FALLBACK_DATA_DIR).join(subdir);
if !dirs.contains(&fallback) {
dirs.push(fallback);
}
dirs
}
/// Represents an available login session.
#[derive(Debug, Clone)]
@@ -82,10 +108,8 @@ pub fn get_sessions(
wayland_dirs: Option<&[PathBuf]>,
xsession_dirs: Option<&[PathBuf]>,
) -> Vec<Session> {
let default_wayland: Vec<PathBuf> =
DEFAULT_WAYLAND_DIRS.iter().map(PathBuf::from).collect();
let default_xsession: Vec<PathBuf> =
DEFAULT_XSESSION_DIRS.iter().map(PathBuf::from).collect();
let default_wayland = session_search_dirs("wayland-sessions");
let default_xsession = session_search_dirs("xsessions");
let wayland = wayland_dirs.unwrap_or(&default_wayland);
let xsession = xsession_dirs.unwrap_or(&default_xsession);
@@ -129,6 +153,52 @@ mod tests {
fs::write(dir.join(name), content).unwrap();
}
#[test]
fn search_dirs_walk_xdg_data_dirs() {
let dirs = session_search_dirs_from(
"/run/current-system/sw/share:/usr/local/share",
"wayland-sessions",
);
assert_eq!(
dirs,
vec![
PathBuf::from("/run/current-system/sw/share/wayland-sessions"),
PathBuf::from("/usr/local/share/wayland-sessions"),
PathBuf::from("/usr/share/wayland-sessions"),
]
);
}
#[test]
fn search_dirs_fall_back_when_unset() {
let dirs = session_search_dirs_from("", "xsessions");
assert_eq!(dirs, vec![PathBuf::from("/usr/share/xsessions")]);
}
#[test]
fn search_dirs_do_not_duplicate_fallback() {
let dirs = session_search_dirs_from("/usr/share:/opt/share", "wayland-sessions");
assert_eq!(
dirs,
vec![
PathBuf::from("/usr/share/wayland-sessions"),
PathBuf::from("/opt/share/wayland-sessions"),
]
);
}
#[test]
fn search_dirs_skip_empty_segments() {
let dirs = session_search_dirs_from("::/opt/share:", "wayland-sessions");
assert_eq!(
dirs,
vec![
PathBuf::from("/opt/share/wayland-sessions"),
PathBuf::from("/usr/share/wayland-sessions"),
]
);
}
#[test]
fn parse_valid_desktop_file() {
let dir = tempfile::tempdir().unwrap();