Share no ssh connection when the socket path would be too long

macOS accepts socket paths of at most 103 characters. Count the folder,
the file name, the 40-character hash and the temporary end ssh adds; when
the total is over 103, leave the connection-sharing options out. Find,
make and check the socket folder once per run instead of for every
command.
This commit is contained in:
2026-10-15 15:31:08 +02:00
parent 25308f5f0f
commit bd7c129116
2 changed files with 73 additions and 5 deletions
+67 -4
View File
@@ -75,6 +75,33 @@ fn socket_dir(runtime: Option<PathBuf>, cache: Option<PathBuf>) -> Option<PathBu
Some(dir)
}
/// The start of every socket's file name; ssh puts `%C`, a hash of the connection, after it.
#[cfg(not(windows))]
const SOCKET_PREFIX: &str = "gites-";
/// The longest socket path macOS accepts (Linux accepts 107). One limit for every system
/// keeps one rule.
#[cfg(not(windows))]
const MAX_SOCKET_PATH: usize = 103;
/// True when sockets in `dir` stay short enough for the system. ssh puts in place of `%C` a
/// hash of 40 characters, and while it sets up a shared connection it adds a temporary end of
/// up to 18 characters to the file name.
#[cfg(not(windows))]
fn socket_path_fits(dir: &std::path::Path) -> bool {
const HASH: usize = 40;
const TEMPORARY_END: usize = 18;
// The 1 is the `/` between the folder and the file name.
dir.as_os_str().len() + 1 + SOCKET_PREFIX.len() + HASH + TEMPORARY_END <= MAX_SOCKET_PATH
}
/// The path ssh is given for the socket of shared connections, or `None` to share none: when
/// there is no folder for sockets, or the path would be too long.
#[cfg(not(windows))]
fn control_path(dir: Option<PathBuf>) -> Option<PathBuf> {
dir.filter(|d| socket_path_fits(d)).map(|d| d.join(format!("{SOCKET_PREFIX}%C")))
}
/// Runs git in the folder `loc` names: here, or on another machine through ssh.
pub fn git(loc: &Location, args: &[&str]) -> Result<String, String> {
// Tests swap in a script that acts like ssh.
@@ -104,11 +131,17 @@ pub fn git_via(ssh: &[OsString], loc: &Location, args: &[&str]) -> Result<String
cmd.args(["-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=2"]);
// Later commands to the same server reuse one connection for a while. Windows ssh
// does not support this.
// The folder is found, made and checked once per run, not for every command.
#[cfg(not(windows))]
if let Some(dir) = socket_dir(dirs::runtime_dir(), dirs::cache_dir()) {
cmd.args(["-o", "ControlMaster=auto", "-o"]);
cmd.arg(format!("ControlPath={}", dir.join("gites-%C").display()));
cmd.args(["-o", "ControlPersist=10m"]);
{
static CONTROL_PATH: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
let path = CONTROL_PATH
.get_or_init(|| control_path(socket_dir(dirs::runtime_dir(), dirs::cache_dir())));
if let Some(path) = path {
cmd.args(["-o", "ControlMaster=auto", "-o"]);
cmd.arg(format!("ControlPath={}", path.display()));
cmd.args(["-o", "ControlPersist=10m"]);
}
}
// `--` keeps a target from being read as an option.
cmd.arg("--").arg(target).arg(remote_command(path, args));
@@ -399,6 +432,36 @@ mod tests {
let _ = std::fs::remove_dir_all(&base);
}
#[cfg(not(windows))]
#[test]
fn shares_connections_through_a_socket_in_a_short_folder() {
assert!(socket_path_fits(&PathBuf::from("/run/user/1000")));
assert_eq!(
control_path(Some(PathBuf::from("/run/user/1000"))),
Some(PathBuf::from("/run/user/1000/gites-%C"))
);
}
#[cfg(not(windows))]
#[test]
fn shares_no_connection_when_the_socket_path_would_be_too_long() {
// The longest folder that fits: the rest of the path takes 1 + 6 + 40 + 18 characters.
let longest = 103 - 1 - SOCKET_PREFIX.len() - 40 - 18;
let too_long = PathBuf::from(format!("/{}", "a".repeat(longest)));
assert!(!socket_path_fits(&too_long));
assert_eq!(control_path(Some(too_long)), None);
assert_eq!(control_path(None), None);
}
#[cfg(not(windows))]
#[test]
fn shares_connections_when_the_socket_path_is_exactly_at_the_limit() {
let longest = 103 - 1 - SOCKET_PREFIX.len() - 40 - 18;
let at_limit = PathBuf::from(format!("/{}", "a".repeat(longest - 1)));
assert_eq!(at_limit.as_os_str().len(), longest);
assert!(socket_path_fits(&at_limit));
}
#[test]
fn reports_a_missing_ssh_program() {
let loc = Location::Ssh { target: "myserver".into(), path: "/x".into() };