use crate::plugins::ProgramWriter; use anyhow::{Context, Result, anyhow}; use log::*; use std::env; use std::fs; use std::io; use std::io::Write; use std::os::unix::fs::PermissionsExt; use std::process::{Command, Stdio}; use crate::meta_plugin::MetaPlugin; #[derive(Clone, Debug)] pub struct MetaPluginProgram { pub program: String, pub args: Vec, pub supported: bool, pub meta_name: String, pub split_whitespace: bool, buffer: Vec, } impl MetaPluginProgram { pub fn new(program: &str, args: Vec<&str>, meta_name: String, split_whitespace: bool) -> MetaPluginProgram { let program_path = get_program_path(program); let supported = program_path.is_ok(); MetaPluginProgram { program: program_path.unwrap_or(program.to_string()), args: args.iter().map(|s| s.to_string()).collect(), supported, meta_name, split_whitespace, buffer: Vec::new(), } } } impl MetaPlugin for MetaPluginProgram { fn is_supported(&self) -> bool { self.supported } fn create(&self) -> Result> { debug!("META: Writing using {:?}", *self); let program = self.program.clone(); let args = self.args.clone(); debug!("META: Executing command: {:?} {:?}", program, args); let mut process = Command::new(program.clone()) .args(args.clone()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .context(anyhow!( "Problem spawning child process: {:?} {:?}", program, args ))?; Ok(Box::new(ProgramWriter { stdin: process.stdin.take().unwrap(), })) } fn finalize(&mut self) -> io::Result { let program = self.program.clone(); let args = self.args.clone(); debug!("META: Executing command for finalize: {:?} {:?}", program, args); let mut process = Command::new(program) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to spawn process: {}", e)))?; let stdin = process.stdin.as_mut().unwrap(); stdin.write_all(&self.buffer) .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to write to stdin: {}", e)))?; let output = process.wait_with_output() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to wait for process: {}", e)))?; if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); let trimmed_result = stdout.trim(); // For certain programs, we only want the first part before whitespace if self.split_whitespace { let parts: Vec<&str> = trimmed_result.split_whitespace().collect(); if !parts.is_empty() { Ok(parts[0].to_string()) } else { Ok(trimmed_result.to_string()) } } else { Ok(trimmed_result.to_string()) } } else { let stderr = String::from_utf8_lossy(&output.stderr); Err(io::Error::new( io::ErrorKind::Other, format!("Command failed: {}", stderr.trim()), )) } } fn update(&mut self, data: &[u8]) { self.buffer.extend_from_slice(data); } fn meta_name(&mut self) -> String { self.meta_name.clone() } } fn get_program_path(program: &str) -> Result { debug!("META: Looking for executable: {}", program); if let Ok(path) = env::var("PATH") { for p in path.split(':') { let p_str = format!("{}/{}", p, program); let stat = fs::metadata(p_str.clone()); if let Ok(stat) = stat { let md = stat; let permissions = md.permissions(); if md.is_file() && permissions.mode() & 0o111 != 0 { return Ok(p_str); } } } } Err(anyhow!("Unable to find binary {} in PATH", program)) }