Fix Unix socket connections through long symlink paths (#48772)

## Why

An advertised control socket path can exceed the Unix socket path limit even when its symlink target is short enough to connect to.

## What changed

On Unix, resolve the socket path and retry the connection when the initial attempt returns `InvalidInput`.

## Testing

Add a regression test that creates control sockets under two long home paths, checks that their symlink targets are distinct, and verifies that clients can connect through both advertised paths.

GitOrigin-RevId: 2541f0a9b02d85035fa89fe26bbcd36d5cd0cc1e
This commit is contained in:
Eric Traut
2026-09-27 19:14:24 +00:00
committed by copyberry
parent 32f5784851
commit fdbce2080c
2 changed files with 50 additions and 1 deletions
@@ -55,6 +55,48 @@ fn listen_unix_socket_accepts_relative_custom_path() {
);
}
#[cfg(unix)]
#[tokio::test]
async fn long_control_socket_paths_connect_to_distinct_daemons() {
let temp_dir = tempfile::TempDir::new().expect("temp dir");
let long_parent = temp_dir.path().join("x".repeat(120));
let mut sockets = Vec::new();
let mut acceptors = Vec::new();
for name in ["first", "second"] {
let codex_home = long_parent.join(name);
std::fs::create_dir_all(&codex_home).expect("codex home");
let socket_path = app_server_control_socket_path(&codex_home).expect("socket path");
let (transport_event_tx, _transport_event_rx) =
mpsc::channel::<TransportEvent>(CHANNEL_CAPACITY);
let shutdown_token = CancellationToken::new();
let acceptor = start_control_socket_acceptor(
socket_path.clone(),
transport_event_tx,
shutdown_token.clone(),
DaemonShutdownAccess::Disabled,
)
.await
.expect("control socket acceptor should start");
sockets.push(socket_path);
acceptors.push((shutdown_token, acceptor));
}
assert_ne!(
std::fs::read_link(sockets[0].as_path()).expect("first socket target"),
std::fs::read_link(sockets[1].as_path()).expect("second socket target")
);
for socket in &sockets {
connect_to_socket(socket.as_path())
.await
.expect("client should connect through long path");
}
for (shutdown_token, acceptor) in acceptors {
shutdown_token.cancel();
acceptor.await.expect("acceptor should stop");
}
}
#[tokio::test]
async fn control_socket_acceptor_upgrades_and_forwards_websocket_text_messages_and_pings() {
let temp_dir = tempfile::TempDir::new().expect("temp dir");
+8 -1
View File
@@ -164,7 +164,14 @@ mod platform {
}
pub(super) async fn connect_stream(socket_path: &Path) -> IoResult<Stream> {
UnixStream::connect(socket_path).await
match UnixStream::connect(socket_path).await {
Err(err) if err.kind() == ErrorKind::InvalidInput => {
// The advertised path may exceed sun_path even when its symlink
// target is a short socket path. Resolve it before retrying.
UnixStream::connect(fs::canonicalize(socket_path).await?).await
}
result => result,
}
}
pub(super) async fn is_stale_socket_path(socket_path: &Path) -> IoResult<bool> {