140 lines
4.3 KiB
Rust
140 lines
4.3 KiB
Rust
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<String>,
|
|
pub supported: bool,
|
|
pub meta_name: String,
|
|
pub split_whitespace: bool,
|
|
buffer: Vec<u8>,
|
|
}
|
|
|
|
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<Box<dyn Write>> {
|
|
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<String> {
|
|
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<String> {
|
|
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))
|
|
}
|