use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use tokio::process::{Child, Command};
use crate::{AutosshError, MonitorMode};
pub fn resolve_ssh_path(
autossh_path_env: Option<&OsStr>,
path_env: Option<&OsStr>,
) -> Result<PathBuf, AutosshError> {
if let Some(p) = autossh_path_env {
let candidate = PathBuf::from(p);
return Ok(candidate);
}
let Some(path) = path_env else {
return Err(AutosshError::SshNotFound {
searched: Vec::new(),
});
};
let mut searched = Vec::new();
for dir in std::env::split_paths(path) {
searched.push(dir.clone());
#[cfg(windows)]
{
let with_exe = dir.join("ssh.exe");
if with_exe.is_file() {
return Ok(with_exe);
}
let bare = dir.join("ssh");
if bare.is_file() {
return Ok(bare);
}
}
#[cfg(unix)]
{
let bare = dir.join("ssh");
if bare.is_file() {
return Ok(bare);
}
}
}
Err(AutosshError::SshNotFound { searched })
}
pub fn inject_monitor_forwards(mode: &MonitorMode, ssh_args: &[String]) -> Vec<String> {
match mode {
MonitorMode::None => ssh_args.to_vec(),
MonitorMode::Active { port, echo: None } => {
let pair = format!("{port}:127.0.0.1:{}", port.saturating_add(1));
let mut out = Vec::with_capacity(ssh_args.len() + 4);
out.push("-L".to_string());
out.push(pair.clone());
out.push("-R".to_string());
out.push(pair);
out.extend(ssh_args.iter().cloned());
out
}
MonitorMode::Active {
port,
echo: Some(echo),
} => {
let pair = format!("{port}:127.0.0.1:{echo}");
let mut out = Vec::with_capacity(ssh_args.len() + 2);
out.push("-L".to_string());
out.push(pair);
out.extend(ssh_args.iter().cloned());
out
}
}
}
pub async fn spawn_ssh(ssh_path: &Path, args: &[String]) -> Result<Child, AutosshError> {
let mut cmd = Command::new(ssh_path);
cmd.args(args);
#[cfg(unix)]
{
cmd.process_group(0);
}
#[cfg(windows)]
{
const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
cmd.creation_flags(CREATE_NEW_PROCESS_GROUP);
}
let child = cmd.spawn()?;
Ok(child)
}