arcbox_virtio_console/
socket.rs1use std::io::{Read, Write};
7use std::os::unix::net::{UnixListener, UnixStream};
8use std::path::{Path, PathBuf};
9
10use crate::ConsoleIo;
11
12pub struct SocketConsole {
17 listener: Option<UnixListener>,
19 client: Option<UnixStream>,
21 path: PathBuf,
23}
24
25impl SocketConsole {
26 pub fn new(path: impl Into<PathBuf>) -> std::io::Result<Self> {
32 let path = path.into();
33
34 let _ = std::fs::remove_file(&path);
35
36 let listener = UnixListener::bind(&path)?;
37 listener.set_nonblocking(true)?;
38
39 tracing::info!("Created socket console: {}", path.display());
40
41 Ok(Self {
42 listener: Some(listener),
43 client: None,
44 path,
45 })
46 }
47
48 #[must_use]
50 pub fn path(&self) -> &Path {
51 &self.path
52 }
53
54 pub fn accept(&mut self) -> std::io::Result<bool> {
56 if let Some(listener) = &self.listener {
57 match listener.accept() {
58 Ok((stream, _)) => {
59 stream.set_nonblocking(true)?;
60 self.client = Some(stream);
61 tracing::info!("Console client connected");
62 Ok(true)
63 }
64 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(false),
65 Err(e) => Err(e),
66 }
67 } else {
68 Ok(false)
69 }
70 }
71
72 #[must_use]
74 pub const fn is_connected(&self) -> bool {
75 self.client.is_some()
76 }
77}
78
79impl Drop for SocketConsole {
80 fn drop(&mut self) {
81 self.client = None;
82 self.listener = None;
83 let _ = std::fs::remove_file(&self.path);
84 }
85}
86
87impl ConsoleIo for SocketConsole {
88 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
89 let _ = self.accept();
91
92 if let Some(client) = &mut self.client {
93 match client.read(buf) {
94 Ok(0) => {
95 self.client = None;
97 Ok(0)
98 }
99 Ok(n) => Ok(n),
100 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(0),
101 Err(e) => Err(e),
102 }
103 } else {
104 Ok(0)
105 }
106 }
107
108 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
109 if let Some(client) = &mut self.client {
110 match client.write(buf) {
111 Ok(n) => Ok(n),
112 Err(e) => {
113 self.client = None;
115 Err(e)
116 }
117 }
118 } else {
119 Ok(buf.len())
121 }
122 }
123
124 fn flush(&mut self) -> std::io::Result<()> {
125 if let Some(client) = &mut self.client {
126 client.flush()
127 } else {
128 Ok(())
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn test_socket_console_creation() {
139 let temp_dir = std::env::temp_dir();
140 let socket_path = temp_dir.join(format!("test_console_{}.sock", std::process::id()));
141
142 let console = SocketConsole::new(&socket_path).unwrap();
143 assert_eq!(console.path(), socket_path);
144 assert!(!console.is_connected());
145 }
146
147 #[test]
148 fn test_socket_console_no_client() {
149 let temp_dir = std::env::temp_dir();
150 let socket_path = temp_dir.join(format!(
151 "test_console_no_client_{}.sock",
152 std::process::id()
153 ));
154
155 let mut console = SocketConsole::new(&socket_path).unwrap();
156
157 let mut buf = [0u8; 10];
158 let n = console.read(&mut buf).unwrap();
159 assert_eq!(n, 0);
160
161 let written = console.write(b"test").unwrap();
162 assert_eq!(written, 4);
163 }
164
165 #[test]
166 fn test_socket_console_accept_no_client() {
167 let temp_dir = std::env::temp_dir();
168 let socket_path = temp_dir.join(format!("test_console_accept_{}.sock", std::process::id()));
169
170 let mut console = SocketConsole::new(&socket_path).unwrap();
171
172 assert!(!console.accept().unwrap());
173 }
174
175 #[test]
176 fn test_socket_console_client_connect() {
177 let temp_dir = std::env::temp_dir();
178 let socket_path =
179 temp_dir.join(format!("test_console_connect_{}.sock", std::process::id()));
180
181 let mut console = SocketConsole::new(&socket_path).unwrap();
182
183 let _client = UnixStream::connect(&socket_path).unwrap();
184
185 assert!(console.accept().unwrap());
186 assert!(console.is_connected());
187 }
188
189 #[test]
190 fn test_socket_console_read_write_with_client() {
191 let temp_dir = std::env::temp_dir();
192 let socket_path = temp_dir.join(format!("test_console_rw_{}.sock", std::process::id()));
193
194 let mut console = SocketConsole::new(&socket_path).unwrap();
195
196 let mut client = UnixStream::connect(&socket_path).unwrap();
197 client.set_nonblocking(true).unwrap();
198 console.accept().unwrap();
199
200 console.write(b"Hello").unwrap();
201
202 let mut buf = [0u8; 10];
203 std::thread::sleep(std::time::Duration::from_millis(10));
204 let n = client.read(&mut buf).unwrap_or(0);
205 if n > 0 {
206 assert_eq!(&buf[..n], b"Hello");
207 }
208
209 client.write_all(b"World").unwrap();
210
211 std::thread::sleep(std::time::Duration::from_millis(10));
212 let mut buf2 = [0u8; 10];
213 let n2 = console.read(&mut buf2).unwrap();
214 if n2 > 0 {
215 assert_eq!(&buf2[..n2], b"World");
216 }
217 }
218
219 #[test]
220 fn test_socket_console_flush() {
221 let temp_dir = std::env::temp_dir();
222 let socket_path = temp_dir.join(format!("test_console_flush_{}.sock", std::process::id()));
223
224 let mut console = SocketConsole::new(&socket_path).unwrap();
225
226 assert!(console.flush().is_ok());
227 }
228
229 #[test]
230 fn test_socket_console_cleanup() {
231 let temp_dir = std::env::temp_dir();
232 let socket_path =
233 temp_dir.join(format!("test_console_cleanup_{}.sock", std::process::id()));
234
235 {
236 let _console = SocketConsole::new(&socket_path).unwrap();
237 assert!(socket_path.exists());
238 }
239
240 assert!(!socket_path.exists());
241 }
242}