diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-05 19:34:24 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-05 19:34:24 +0300 |
| commit | 2863b715132fe29e53089e06f105943d1df0b3b4 (patch) | |
| tree | 6b4af2f6fdec6de2888fd2bff3c0829d6719b727 /src/platform/unix_common.rs | |
| parent | eb93ec8ce62b05de776f751f4d2f52ff60f93f16 (diff) | |
feat(windows): support remote attach to unix hosts (#2329)
* feat(windows): support remote attach to unix hosts
* fix(remote): preserve unix bridge behavior
Diffstat (limited to 'src/platform/unix_common.rs')
| -rw-r--r-- | src/platform/unix_common.rs | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/src/platform/unix_common.rs b/src/platform/unix_common.rs new file mode 100644 index 00000000..754e2fc2 --- /dev/null +++ b/src/platform/unix_common.rs @@ -0,0 +1,131 @@ +use std::path::{Path, PathBuf}; + +pub(crate) fn remote_ssh_config_paths() -> super::RemoteSshConfigPaths { + super::RemoteSshConfigPaths { + user_config: std::env::var_os("HOME") + .map(PathBuf::from) + .map(|home| home.join(".ssh").join("config")), + system_config: Some(PathBuf::from("/etc/ssh/ssh_config")), + multiplexing: true, + } +} + +pub(crate) fn create_remote_ssh_config_dir(control_socket_name: &str) -> std::io::Result<PathBuf> { + use std::os::unix::fs::DirBuilderExt; + + let mut bases = vec![std::env::temp_dir()]; + let short_tmp = PathBuf::from("/tmp"); + if bases.first() != Some(&short_tmp) { + bases.push(short_tmp); + } + + let mut last_error = None; + let mut path_fits = false; + for base in bases { + for attempt in 0..100 { + let dir = base.join(format!("herdr-ssh-{}-{attempt}", std::process::id())); + if !fits_unix_socket_path(&dir.join(control_socket_name)) { + continue; + } + path_fits = true; + match std::fs::DirBuilder::new().mode(0o700).create(&dir) { + Ok(()) => return Ok(dir), + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(err) => { + last_error = Some(err); + break; + } + } + } + } + + if let Some(err) = last_error { + return Err(err); + } + let message = if path_fits { + "failed to create private herdr ssh config directory" + } else { + "SSH control socket path exceeds the Unix socket length limit" + }; + Err(std::io::Error::new( + if path_fits { + std::io::ErrorKind::AlreadyExists + } else { + std::io::ErrorKind::InvalidInput + }, + message, + )) +} + +pub(crate) fn create_remote_ssh_config_file(path: &Path) -> std::io::Result<std::fs::File> { + use std::os::unix::fs::OpenOptionsExt; + + std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(path) +} + +pub(crate) fn create_remote_private_dir(path: &Path) -> std::io::Result<()> { + use std::os::unix::fs::DirBuilderExt; + + std::fs::DirBuilder::new().mode(0o700).create(path) +} + +pub(crate) fn remote_private_temp_base() -> PathBuf { + std::env::temp_dir() +} + +pub(crate) fn remote_bridge_endpoint_path(readable_name: &str, short_name: &str) -> PathBuf { + let tmp = std::env::temp_dir(); + let readable = tmp.join(readable_name); + if fits_unix_socket_path(&readable) { + return readable; + } + let short = tmp.join(short_name); + if fits_unix_socket_path(&short) { + return short; + } + PathBuf::from("/tmp").join(short_name) +} + +pub(crate) fn remote_reattach_program(program: &str) -> String { + shell_quote(if program.is_empty() { "herdr" } else { program }) +} + +pub(crate) fn remote_reattach_argument(value: &str) -> String { + shell_quote(value) +} + +fn shell_quote(value: &str) -> String { + if !value.is_empty() + && value.chars().all(|ch| { + ch.is_ascii_alphanumeric() + || matches!( + ch, + '@' | '%' | '_' | '+' | '=' | ':' | ',' | '.' | '/' | '-' + ) + }) + { + return value.to_string(); + } + format!("'{}'", value.replace('\'', "'\\''")) +} + +fn fits_unix_socket_path(path: &Path) -> bool { + use std::os::unix::ffi::OsStrExt; + + path.as_os_str().as_bytes().len() <= 103 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn remote_ssh_config_dir_rejects_overlong_control_socket_name() { + let err = create_remote_ssh_config_dir(&"x".repeat(200)).unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); + } +} |
