waos_dev_protocol/
socket.rs1use std::fs::{self, DirBuilder, Permissions};
12use std::io::{self, ErrorKind};
13use std::os::unix::fs::{DirBuilderExt, PermissionsExt};
14use std::path::{Path, PathBuf};
15
16use tokio::net::unix::{OwnedReadHalf, OwnedWriteHalf};
17use tokio::net::{UnixListener, UnixStream};
18
19use crate::connection::Connection;
20
21pub fn socket_path() -> PathBuf {
27 user_folder().join(SOCKET_FILE)
28}
29
30const SOCKET_FILE: &str = "dev.sock";
31
32#[cfg(target_vendor = "apple")]
33fn user_folder() -> PathBuf {
34 home().join("Library/Application Support/WaOS")
35}
36
37#[cfg(not(target_vendor = "apple"))]
38fn user_folder() -> PathBuf {
39 match std::env::var_os("XDG_RUNTIME_DIR") {
40 Some(runtime) if !runtime.is_empty() => PathBuf::from(runtime).join("waos"),
41 _ => home().join(".local/share/waos"),
42 }
43}
44
45fn home() -> PathBuf {
48 std::env::home_dir().unwrap_or_else(|| PathBuf::from("/"))
49}
50
51impl Connection<UnixStream> {
52 pub async fn connect(path: &Path) -> io::Result<Connection<UnixStream>> {
54 Ok(Connection::new(UnixStream::connect(path).await?))
55 }
56
57 pub fn into_split(self) -> (Connection<OwnedReadHalf>, Connection<OwnedWriteHalf>) {
59 let (reading, writing) = self.stream.into_split();
60 (Connection::new(reading), Connection::new(writing))
61 }
62}
63
64#[derive(Debug)]
67pub struct Listener {
68 listener: UnixListener,
69 path: PathBuf,
70}
71
72impl Listener {
73 pub async fn bind(path: &Path) -> io::Result<Listener> {
77 make_private_folder(path)?;
78 clear_stale_socket(path).await?;
79 let listener = UnixListener::bind(path)?;
80 fs::set_permissions(path, Permissions::from_mode(0o600))?;
81 Ok(Listener {
82 listener,
83 path: path.to_owned(),
84 })
85 }
86
87 pub async fn accept(&self) -> io::Result<Connection<UnixStream>> {
89 let (stream, _) = self.listener.accept().await?;
90 Ok(Connection::new(stream))
91 }
92}
93
94impl Drop for Listener {
95 fn drop(&mut self) {
96 let _ = fs::remove_file(&self.path);
98 }
99}
100
101fn make_private_folder(socket: &Path) -> io::Result<()> {
103 let Some(folder) = socket.parent() else {
104 return Ok(());
105 };
106 DirBuilder::new().recursive(true).mode(0o700).create(folder)
107}
108
109async fn clear_stale_socket(path: &Path) -> io::Result<()> {
112 match UnixStream::connect(path).await {
113 Ok(_answering) => Err(io::Error::new(
114 ErrorKind::AddrInUse,
115 format!("another WaOS answers at {}", path.display()),
116 )),
117 Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
118 Err(error) if error.kind() == ErrorKind::ConnectionRefused => {
119 tracing::info!(path = %path.display(), "replacing the socket file a WaOS that died left");
120 fs::remove_file(path)
121 }
122 Err(error) => Err(error),
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn the_socket_is_in_the_user_folder_under_its_name() {
132 let path = socket_path();
133 assert!(path.starts_with(home()), "{}", path.display());
134 assert!(path.ends_with(SOCKET_FILE), "{}", path.display());
135 }
136}