Skip to main content

wcode_protocol/
socket.rs

1//! Unix-socket binding and connecting.
2//!
3//! One socket per session (the design's transport #2: `wcode serve` owns the
4//! session, a client connects). A stale socket file left by a crashed server is
5//! removed before binding, so a restart is not blocked by its predecessor.
6
7use std::io;
8use std::os::unix::fs::PermissionsExt;
9use std::path::Path;
10
11use tokio::net::{UnixListener, UnixStream};
12
13/// Bind `path`, creating its parent directory and clearing a stale socket.
14pub async fn bind(path: &Path) -> io::Result<UnixListener> {
15    if let Some(dir) = path.parent()
16        && !dir.as_os_str().is_empty()
17    {
18        std::fs::create_dir_all(dir)?;
19    }
20    // A crashed server leaves the socket file behind; `UnixListener::bind`
21    // would then fail with `AddrInUse`. Remove it first.
22    match std::fs::remove_file(path) {
23        Ok(()) => {}
24        Err(e) if e.kind() == io::ErrorKind::NotFound => {}
25        Err(e) => return Err(e),
26    }
27    let listener = UnixListener::bind(path)?;
28    // The socket file follows umask by default (commonly 0755/0775); it is the
29    // session's only credential, so narrow it to owner-only. A bind that cannot
30    // chmod its own just-created socket fails loudly rather than shipping an
31    // over-permissive socket.
32    std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
33    Ok(listener)
34}
35
36/// Connect to a server listening at `path`.
37pub async fn connect(path: &Path) -> io::Result<UnixStream> {
38    UnixStream::connect(path).await
39}