Skip to main content

scxtui/
system.rs

1// SPDX-License-Identifier: GPL-2.0
2
3//! Programs scxtui starts (`scxcli`, `pkexec`, `journalctl`, `scxtop`),
4//! resolved in systemd's default search path rather than scxtui's PATH.
5//! scxtui may run as root, and a user-writable directory ahead of
6//! `/usr/bin` in root's PATH would let anyone substitute one of them.
7
8use std::os::unix::fs::PermissionsExt;
9use std::path::{Path, PathBuf};
10use std::process::Command;
11
12use anyhow::{Context, Result, bail};
13
14/// Where `systemd-path` lives. Called by absolute path with a cleared
15/// environment, so the directory list scxtui trusts cannot itself come
16/// from the caller's PATH.
17const SYSTEMD_PATH: &str = "/usr/bin/systemd-path";
18
19pub fn system_command(name: &str) -> Result<Command> {
20    Ok(Command::new(system_executable(name)?))
21}
22
23/// The absolute path a program resolves to in systemd's default search
24/// path.
25pub fn system_executable(name: &str) -> Result<PathBuf> {
26    let dirs = system_binary_dirs()?;
27    dirs.iter()
28        .map(|dir| dir.join(name))
29        .find(|candidate| is_executable_file(candidate))
30        .with_context(|| format!("{name} is not an executable in systemd's default search path"))
31}
32
33fn system_binary_dirs() -> Result<Vec<PathBuf>> {
34    let out = Command::new(SYSTEMD_PATH)
35        .arg("search-binaries-default")
36        .env_clear()
37        .output()
38        .with_context(|| format!("cannot run {SYSTEMD_PATH}"))?;
39    if !out.status.success() {
40        bail!(
41            "{SYSTEMD_PATH} search-binaries-default failed: {}",
42            String::from_utf8_lossy(&out.stderr).trim()
43        );
44    }
45    let text = std::str::from_utf8(&out.stdout).context("systemd-path printed non-UTF-8")?;
46    let dirs: Vec<PathBuf> = std::env::split_paths(text.trim()).collect();
47    if dirs.is_empty() || dirs.iter().any(|dir| !dir.is_absolute()) {
48        bail!(
49            "systemd-path returned an invalid search path: {:?}",
50            text.trim()
51        );
52    }
53    Ok(dirs)
54}
55
56fn is_executable_file(path: &Path) -> bool {
57    std::fs::metadata(path)
58        .is_ok_and(|meta| meta.is_file() && meta.permissions().mode() & 0o111 != 0)
59}