aboutsummaryrefslogtreecommitdiff
path: root/src/platform/unix_common.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-05 19:34:24 +0300
committerGitHub <noreply@github.com>2026-08-05 19:34:24 +0300
commit2863b715132fe29e53089e06f105943d1df0b3b4 (patch)
tree6b4af2f6fdec6de2888fd2bff3c0829d6719b727 /src/platform/unix_common.rs
parenteb93ec8ce62b05de776f751f4d2f52ff60f93f16 (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.rs131
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);
+ }
+}