arcbox_virtio_console/
pty.rs1use std::os::unix::io::RawFd;
7use std::sync::{Mutex, PoisonError};
8
9use crate::ConsoleIo;
10
11static PTY_ALLOC_LOCK: Mutex<()> = Mutex::new(());
18
19pub struct PtyConsole {
24 master_fd: RawFd,
26 slave_fd: RawFd,
28 slave_path: String,
30 nonblocking: bool,
32}
33
34impl PtyConsole {
35 pub fn new() -> std::io::Result<Self> {
41 let mut master_fd: libc::c_int = -1;
42 let mut slave_fd: libc::c_int = -1;
43
44 let alloc = PTY_ALLOC_LOCK
47 .lock()
48 .unwrap_or_else(PoisonError::into_inner);
49
50 let ret = unsafe {
54 libc::openpty(
55 &mut master_fd,
56 &mut slave_fd,
57 std::ptr::null_mut(),
58 std::ptr::null_mut(),
59 std::ptr::null_mut(),
60 )
61 };
62
63 if ret < 0 {
64 return Err(std::io::Error::last_os_error());
65 }
66
67 let mut name_buf = [0 as libc::c_char; 256];
68 let err = unsafe { libc::ttyname_r(slave_fd, name_buf.as_mut_ptr(), name_buf.len()) };
72 drop(alloc);
73 if err != 0 {
74 unsafe {
76 libc::close(master_fd);
77 libc::close(slave_fd);
78 }
79 return Err(std::io::Error::from_raw_os_error(err));
80 }
81 let slave_path = unsafe { std::ffi::CStr::from_ptr(name_buf.as_ptr()) }
83 .to_string_lossy()
84 .into_owned();
85
86 tracing::info!(
87 "Created PTY console: master_fd={}, slave={}",
88 master_fd,
89 slave_path
90 );
91
92 Ok(Self {
93 master_fd,
94 slave_fd,
95 slave_path,
96 nonblocking: false,
97 })
98 }
99
100 #[must_use]
106 pub fn slave_path(&self) -> &str {
107 &self.slave_path
108 }
109
110 #[must_use]
112 pub const fn master_fd(&self) -> RawFd {
113 self.master_fd
114 }
115
116 pub fn set_nonblocking(&mut self, nonblocking: bool) -> std::io::Result<()> {
118 let flags = unsafe { libc::fcntl(self.master_fd, libc::F_GETFL) };
121 if flags < 0 {
122 return Err(std::io::Error::last_os_error());
123 }
124
125 let new_flags = if nonblocking {
126 flags | libc::O_NONBLOCK
127 } else {
128 flags & !libc::O_NONBLOCK
129 };
130
131 let ret = unsafe { libc::fcntl(self.master_fd, libc::F_SETFL, new_flags) };
133 if ret < 0 {
134 return Err(std::io::Error::last_os_error());
135 }
136
137 self.nonblocking = nonblocking;
138 Ok(())
139 }
140
141 pub fn set_window_size(&self, rows: u16, cols: u16) -> std::io::Result<()> {
143 let ws = libc::winsize {
144 ws_row: rows,
145 ws_col: cols,
146 ws_xpixel: 0,
147 ws_ypixel: 0,
148 };
149
150 let ret = unsafe { libc::ioctl(self.master_fd, libc::TIOCSWINSZ, &ws) };
153 if ret < 0 {
154 Err(std::io::Error::last_os_error())
155 } else {
156 Ok(())
157 }
158 }
159
160 #[must_use]
162 pub fn has_data(&self) -> bool {
163 let mut pollfd = libc::pollfd {
164 fd: self.master_fd,
165 events: libc::POLLIN,
166 revents: 0,
167 };
168
169 let ret = unsafe { libc::poll(&mut pollfd, 1, 0) };
171 ret > 0 && (pollfd.revents & libc::POLLIN) != 0
172 }
173}
174
175impl Drop for PtyConsole {
176 fn drop(&mut self) {
177 if self.master_fd >= 0 {
179 unsafe { libc::close(self.master_fd) };
180 }
181 if self.slave_fd >= 0 {
182 unsafe { libc::close(self.slave_fd) };
183 }
184 }
185}
186
187impl ConsoleIo for PtyConsole {
188 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
189 let ret = unsafe {
192 libc::read(
193 self.master_fd,
194 buf.as_mut_ptr() as *mut libc::c_void,
195 buf.len(),
196 )
197 };
198
199 if ret < 0 {
200 let err = std::io::Error::last_os_error();
201 if err.kind() == std::io::ErrorKind::WouldBlock {
202 Ok(0)
203 } else {
204 Err(err)
205 }
206 } else {
207 Ok(ret as usize)
208 }
209 }
210
211 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
212 let ret = unsafe {
215 libc::write(
216 self.master_fd,
217 buf.as_ptr() as *const libc::c_void,
218 buf.len(),
219 )
220 };
221
222 if ret < 0 {
223 Err(std::io::Error::last_os_error())
224 } else {
225 Ok(ret as usize)
226 }
227 }
228
229 fn flush(&mut self) -> std::io::Result<()> {
230 Ok(())
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_pty_console_creation() {
241 let pty = PtyConsole::new().unwrap();
242 assert!(!pty.slave_path().is_empty());
243 assert!(pty.master_fd() >= 0);
244 }
245
246 #[test]
247 fn test_pty_console_write_read() {
248 let mut pty = PtyConsole::new().unwrap();
249 pty.set_nonblocking(true).unwrap();
250
251 let written = pty.write(b"test\n").unwrap();
252 assert!(written > 0);
253
254 }
257
258 #[test]
259 fn test_pty_console_nonblocking() {
260 let mut pty = PtyConsole::new().unwrap();
261
262 pty.set_nonblocking(true).unwrap();
263 assert!(pty.nonblocking);
264
265 let mut buf = [0u8; 10];
266 let n = pty.read(&mut buf).unwrap();
267 assert_eq!(n, 0);
268 }
269
270 #[test]
271 fn test_pty_console_window_size() {
272 let pty = PtyConsole::new().unwrap();
273
274 assert!(pty.set_window_size(24, 80).is_ok());
275 assert!(pty.set_window_size(50, 132).is_ok());
276 }
277
278 #[test]
279 fn test_pty_console_has_data() {
280 let pty = PtyConsole::new().unwrap();
281 assert!(!pty.has_data());
282 }
283
284 #[test]
285 fn test_pty_console_flush() {
286 let mut pty = PtyConsole::new().unwrap();
287 assert!(pty.flush().is_ok());
288 }
289
290 #[test]
294 fn test_pty_console_concurrent_creation() {
295 let threads: Vec<_> = (0..8)
296 .map(|_| {
297 std::thread::spawn(|| {
298 for _ in 0..8 {
299 let pty = PtyConsole::new().unwrap();
300 assert!(pty.slave_path().starts_with("/dev/"));
301 }
302 })
303 })
304 .collect();
305 for t in threads {
306 t.join().unwrap();
307 }
308 }
309}