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}