1use std::os::unix::fs::PermissionsExt;
9use std::path::{Path, PathBuf};
10use std::process::Command;
11
12use anyhow::{Context, Result, bail};
13
14const 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
23pub 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}