1use core::mem::MaybeUninit;
10use core::pin::Pin;
11use core::task::Context;
12use core::task::Poll;
13use std::io;
14use std::os::unix::io::AsRawFd;
15use std::os::unix::io::IntoRawFd;
16use std::os::unix::io::RawFd;
17
18use syscalls::Errno;
19use tokio::io::AsyncRead;
20use tokio::io::AsyncWrite;
21use tokio::io::ReadBuf;
22
23use super::fd::AsyncFd;
24use super::fd::Fd;
25
26#[derive(Debug)]
28pub struct Pty {
29 fd: AsyncFd,
30}
31
32impl Pty {
33 pub fn open() -> Result<Self, Errno> {
38 let fd = Fd::new(Errno::result(unsafe {
39 libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY)
40 })?);
41
42 Errno::result(unsafe { libc::grantpt(fd.as_raw_fd()) })?;
43 Errno::result(unsafe { libc::unlockpt(fd.as_raw_fd()) })?;
44
45 let fd = AsyncFd::new(fd)?;
46
47 Ok(Self { fd })
48 }
49
50 pub fn child(&self) -> Result<PtyChild, Errno> {
52 const TIOCGPTPEER: libc::c_ulong = 0x5441;
53
54 let parent = self.fd.as_raw_fd();
55
56 let fd = Errno::result(unsafe {
57 libc::ioctl(parent, TIOCGPTPEER, libc::O_RDWR | libc::O_NOCTTY)
61 })
62 .map(Fd::new)
63 .or_else(|_err| {
64 let mut path: [libc::c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize];
65
66 Errno::result(unsafe { libc::ptsname_r(parent, path.as_mut_ptr(), path.len()) })?;
67
68 Fd::open_c(path.as_ptr(), libc::O_RDWR | libc::O_NOCTTY)
69 })?;
70
71 Ok(PtyChild { fd })
72 }
73}
74
75#[derive(Debug)]
78pub struct PtyChild {
79 fd: Fd,
80}
81
82impl PtyChild {
83 pub fn login(self) -> Result<(), Errno> {
93 Errno::result(unsafe { libc::login_tty(self.fd.into_raw_fd()) })?;
94 Ok(())
95 }
96
97 pub fn set_window_size(&self, rows: u16, cols: u16) -> Result<(), Errno> {
99 let fd = self.fd.as_raw_fd();
100
101 let winsize = libc::winsize {
102 ws_row: rows,
103 ws_col: cols,
104 ws_xpixel: 0,
105 ws_ypixel: 0,
106 };
107
108 Errno::result(unsafe { libc::ioctl(fd, libc::TIOCSWINSZ, &winsize as *const _) })?;
109
110 Ok(())
111 }
112
113 pub fn window_size(&self) -> Result<(u16, u16), Errno> {
115 let fd = self.fd.as_raw_fd();
116
117 let mut winsize = MaybeUninit::<libc::winsize>::uninit();
118
119 Errno::result(unsafe { libc::ioctl(fd, libc::TIOCGWINSZ, winsize.as_mut_ptr()) })?;
120
121 let winsize = unsafe { winsize.assume_init() };
122
123 Ok((winsize.ws_row, winsize.ws_col))
124 }
125
126 pub fn set_terminal_params(&self, params: &libc::termios) -> Result<(), Errno> {
128 let fd = self.fd.as_raw_fd();
129 Errno::result(unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, params as *const _) })?;
130 Ok(())
131 }
132
133 pub fn terminal_params(&self) -> Result<libc::termios, Errno> {
135 let fd = self.fd.as_raw_fd();
136
137 let mut term = MaybeUninit::<libc::termios>::uninit();
138
139 Errno::result(unsafe { libc::tcgetattr(fd, term.as_mut_ptr()) })?;
140
141 Ok(unsafe { term.assume_init() })
142 }
143}
144
145impl AsRawFd for Pty {
146 fn as_raw_fd(&self) -> RawFd {
147 self.fd.as_raw_fd()
148 }
149}
150
151impl AsRawFd for PtyChild {
152 fn as_raw_fd(&self) -> RawFd {
153 self.fd.as_raw_fd()
154 }
155}
156
157impl AsyncWrite for Pty {
158 fn poll_write(
159 mut self: Pin<&mut Self>,
160 cx: &mut Context<'_>,
161 buf: &[u8],
162 ) -> Poll<tokio::io::Result<usize>> {
163 Pin::new(&mut self.fd).poll_write(cx, buf)
164 }
165
166 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
167 Pin::new(&mut self.fd).poll_flush(cx)
168 }
169
170 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
171 Pin::new(&mut self.fd).poll_shutdown(cx)
172 }
173}
174
175impl AsyncRead for Pty {
176 fn poll_read(
177 mut self: Pin<&mut Self>,
178 cx: &mut Context,
179 buf: &mut ReadBuf,
180 ) -> Poll<tokio::io::Result<()>> {
181 Pin::new(&mut self.fd).poll_read(cx, buf)
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[tokio::test]
190 async fn test_open() {
191 if crate::test_runs_in_own_process() {
192 return;
193 }
194 let pty = Pty::open().unwrap();
195
196 let child1 = pty.child().unwrap();
197 child1.set_window_size(20, 40).unwrap();
198 assert_eq!(child1.window_size().unwrap(), (20, 40));
199
200 let child2 = pty.child().unwrap();
201 child2.set_window_size(40, 80).unwrap();
202
203 assert_eq!(child2.window_size().unwrap(), (40, 80));
204
205 assert_eq!(child1.window_size().unwrap(), (40, 80));
208 }
209}