use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use tokio::process::Command;
use crate::error::{GuixError, PolkitFailure};
pub(crate) const POLKIT_GUIX_PATH: &str = "/run/current-system/profile/bin/guix";
pub(crate) const PKEXEC_PATH: &str = "/run/privileged/bin/pkexec";
pub(crate) fn cmd(program: impl AsRef<OsStr>) -> Command {
let mut c = Command::new(program);
c.env("LC_ALL", "C")
.env("LANG", "C")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
c
}
pub(crate) fn guix_cmd<I, S>(
binary: &Path,
profile: Option<&Path>,
with_profile: bool,
args: I,
) -> Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut c = cmd(binary);
c.kill_on_drop(true);
let mut iter = args.into_iter();
if let Some(subcmd) = iter.next() {
c.arg(subcmd);
if with_profile {
if let Some(p) = profile {
c.arg("-p").arg(p);
}
}
for a in iter {
c.arg(a);
}
}
c
}
pub(crate) async fn run_guix<I, S>(
binary: &Path,
profile: Option<&Path>,
args: I,
) -> Result<Vec<u8>, GuixError>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut c = guix_cmd(binary, profile, true, args);
let out = c.output().await.map_err(GuixError::Spawn)?;
if !out.status.success() {
let code = out.status.code().unwrap_or(-1);
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
return Err(GuixError::NonZeroExit { code, stderr });
}
Ok(out.stdout)
}
fn resolve_pkexec() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("LIBGUIX_PKEXEC") {
return Some(PathBuf::from(p));
}
let preferred = PathBuf::from(PKEXEC_PATH);
if preferred.exists() {
return Some(preferred);
}
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
let candidate = dir.join("pkexec");
if candidate.exists() {
return Some(candidate);
}
}
None
}
pub(crate) fn pkexec_guix_cmd<I, S>(args: I) -> Result<Command, GuixError>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let pkexec = resolve_pkexec().ok_or_else(|| GuixError::Polkit {
kind: PolkitFailure::PkexecMissing,
code: -1,
stderr_tail: "pkexec not found".into(),
})?;
let guix = if let Some(p) = std::env::var_os("LIBGUIX_POLKIT_GUIX") {
PathBuf::from(p)
} else {
let p = PathBuf::from(POLKIT_GUIX_PATH);
if !p.exists() {
return Err(GuixError::NotOnGuixSystem);
}
p
};
let mut c = cmd(pkexec);
c.kill_on_drop(true);
c.arg(guix);
for a in args {
c.arg(a);
}
Ok(c)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_pkexec_env_override_wins() {
let saved = std::env::var_os("LIBGUIX_PKEXEC");
std::env::set_var("LIBGUIX_PKEXEC", "/tmp/libguix-fake-pkexec-xyz");
let r = resolve_pkexec();
assert_eq!(r, Some(PathBuf::from("/tmp/libguix-fake-pkexec-xyz")));
if let Some(v) = saved {
std::env::set_var("LIBGUIX_PKEXEC", v);
} else {
std::env::remove_var("LIBGUIX_PKEXEC");
}
}
}