arcbox_virtio_console/
pty.rs1use std::os::unix::io::RawFd;
7
8use crate::ConsoleIo;
9
10pub struct PtyConsole {
15 master_fd: RawFd,
17 slave_fd: RawFd,
19 slave_path: String,
21 nonblocking: bool,
23}
24
25impl PtyConsole {
26 pub fn new() -> std::io::Result<Self> {
32 let mut master_fd: libc::c_int = -1;
33 let mut slave_fd: libc::c_int = -1;
34
35 let ret = unsafe {
39 libc::openpty(
40 &mut master_fd,
41 &mut slave_fd,
42 std::ptr::null_mut(),
43 std::ptr::null_mut(),
44 std::ptr::null_mut(),
45 )
46 };
47
48 if ret < 0 {
49 return Err(std::io::Error::last_os_error());
50 }
51
52 let slave_path = unsafe {
56 let path_ptr = libc::ttyname(slave_fd);
57 if path_ptr.is_null() {
58 libc::close(master_fd);
59 libc::close(slave_fd);
60 return Err(std::io::Error::last_os_error());
61 }
62 std::ffi::CStr::from_ptr(path_ptr)
63 .to_string_lossy()
64 .into_owned()
65 };
66
67 tracing::info!(
68 "Created PTY console: master_fd={}, slave={}",
69 master_fd,
70 slave_path
71 );
72
73 Ok(Self {
74 master_fd,
75 slave_fd,
76 slave_path,
77 nonblocking: false,
78 })
79 }
80
81 #[must_use]
87 pub fn slave_path(&self) -> &str {
88 &self.slave_path
89 }
90
91 #[must_use]
93 pub const fn master_fd(&self) -> RawFd {
94 self.master_fd
95 }
96
97 pub fn set_nonblocking(&mut self, nonblocking: bool) -> std::io::Result<()> {
99 let flags = unsafe { libc::fcntl(self.master_fd, libc::F_GETFL) };
102 if flags < 0 {
103 return Err(std::io::Error::last_os_error());
104 }
105
106 let new_flags = if nonblocking {
107 flags | libc::O_NONBLOCK
108 } else {
109 flags & !libc::O_NONBLOCK
110 };
111
112 let ret = unsafe { libc::fcntl(self.master_fd, libc::F_SETFL, new_flags) };
114 if ret < 0 {
115 return Err(std::io::Error::last_os_error());
116 }
117
118 self.nonblocking = nonblocking;
119 Ok(())
120 }
121
122 pub fn set_window_size(&self, rows: u16, cols: u16) -> std::io::Result<()> {
124 let ws = libc::winsize {
125 ws_row: rows,
126 ws_col: cols,
127 ws_xpixel: 0,
128 ws_ypixel: 0,
129 };
130
131 let ret = unsafe { libc::ioctl(self.master_fd, libc::TIOCSWINSZ, &ws) };
134 if ret < 0 {
135 Err(std::io::Error::last_os_error())
136 } else {
137 Ok(())
138 }
139 }
140
141 #[must_use]
143 pub fn has_data(&self) -> bool {
144 let mut pollfd = libc::pollfd {
145 fd: self.master_fd,
146 events: libc::POLLIN,
147 revents: 0,
148 };
149
150 let ret = unsafe { libc::poll(&mut pollfd, 1, 0) };
152 ret > 0 && (pollfd.revents & libc::POLLIN) != 0
153 }
154}
155
156impl Drop for PtyConsole {
157 fn drop(&mut self) {
158 if self.master_fd >= 0 {
160 unsafe { libc::close(self.master_fd) };
161 }
162 if self.slave_fd >= 0 {
163 unsafe { libc::close(self.slave_fd) };
164 }
165 }
166}
167
168impl ConsoleIo for PtyConsole {
169 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
170 let ret = unsafe {
173 libc::read(
174 self.master_fd,
175 buf.as_mut_ptr() as *mut libc::c_void,
176 buf.len(),
177 )
178 };
179
180 if ret < 0 {
181 let err = std::io::Error::last_os_error();
182 if err.kind() == std::io::ErrorKind::WouldBlock {
183 Ok(0)
184 } else {
185 Err(err)
186 }
187 } else {
188 Ok(ret as usize)
189 }
190 }
191
192 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
193 let ret = unsafe {
196 libc::write(
197 self.master_fd,
198 buf.as_ptr() as *const libc::c_void,
199 buf.len(),
200 )
201 };
202
203 if ret < 0 {
204 Err(std::io::Error::last_os_error())
205 } else {
206 Ok(ret as usize)
207 }
208 }
209
210 fn flush(&mut self) -> std::io::Result<()> {
211 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_pty_console_creation() {
222 let pty = PtyConsole::new().unwrap();
223 assert!(!pty.slave_path().is_empty());
224 assert!(pty.master_fd() >= 0);
225 }
226
227 #[test]
228 fn test_pty_console_write_read() {
229 let mut pty = PtyConsole::new().unwrap();
230 pty.set_nonblocking(true).unwrap();
231
232 let written = pty.write(b"test\n").unwrap();
233 assert!(written > 0);
234
235 }
238
239 #[test]
240 fn test_pty_console_nonblocking() {
241 let mut pty = PtyConsole::new().unwrap();
242
243 pty.set_nonblocking(true).unwrap();
244 assert!(pty.nonblocking);
245
246 let mut buf = [0u8; 10];
247 let n = pty.read(&mut buf).unwrap();
248 assert_eq!(n, 0);
249 }
250
251 #[test]
252 fn test_pty_console_window_size() {
253 let pty = PtyConsole::new().unwrap();
254
255 assert!(pty.set_window_size(24, 80).is_ok());
256 assert!(pty.set_window_size(50, 132).is_ok());
257 }
258
259 #[test]
260 fn test_pty_console_has_data() {
261 let pty = PtyConsole::new().unwrap();
262 assert!(!pty.has_data());
263 }
264
265 #[test]
266 fn test_pty_console_flush() {
267 let mut pty = PtyConsole::new().unwrap();
268 assert!(pty.flush().is_ok());
269 }
270}