use anyhow::{Context, Result}; use std::{ io::Read, os::fd::AsRawFd, process::{Child, Command, Stdio}, time::Duration, }; pub struct TerminalAgent(Option); impl TerminalAgent { pub fn start(interactive: bool) -> Result { if !interactive || unsafe { libc::geteuid() } == 0 { return Ok(Self(None)); } let (mut reader, writer) = std::os::unix::net::UnixStream::pair()?; reader.set_read_timeout(Some(Duration::from_secs(5)))?; let fd = writer.as_raw_fd(); // SAFETY: fd belongs to writer and remains open until after spawning the agent. unsafe { libc::fcntl(fd, libc::F_SETFD, 0); } let mut child = Command::new("/usr/bin/pkttyagent") .args([ "--process", &std::process::id().to_string(), "--notify-fd", &fd.to_string(), "--fallback", ]) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .spawn() .context("Could not start terminal polkit agent; install polkit")?; drop(writer); let mut buffer = [0u8; 1]; if let Err(error) = reader.read(&mut buffer) { let _ = child.kill(); let _ = child.wait(); return Err(error).context("Terminal authentication agent did not become ready"); } Ok(Self(Some(child))) } } impl Drop for TerminalAgent { fn drop(&mut self) { if let Some(child) = &mut self.0 { let _ = child.kill(); let _ = child.wait(); } } }