Skip to main content

vm_ch/
terminal.rs

1use std::os::fd::RawFd;
2
3/// Saved terminal state for later restoration.
4pub struct TerminalState {
5    fd: RawFd,
6    termios: libc::termios,
7}
8
9impl TerminalState {
10    /// Save the current terminal attributes and switch to raw mode.
11    /// Returns `None` if the fd is not a terminal.
12    pub fn enter_raw_mode(fd: RawFd) -> Option<Self> {
13        unsafe {
14            let mut saved: libc::termios = std::mem::zeroed();
15            if libc::tcgetattr(fd, &mut saved) != 0 {
16                return None;
17            }
18            let mut raw = saved;
19            libc::cfmakeraw(&mut raw);
20            libc::tcsetattr(fd, libc::TCSANOW, &raw);
21            Some(TerminalState { fd, termios: saved })
22        }
23    }
24
25    pub fn restore(&self) {
26        unsafe {
27            libc::tcsetattr(self.fd, libc::TCSANOW, &self.termios);
28        }
29    }
30}
31
32impl Drop for TerminalState {
33    fn drop(&mut self) {
34        self.restore();
35    }
36}
37
38/// Get the terminal size (rows, cols) for the given fd.
39/// Returns (24, 80) as fallback if the ioctl fails.
40pub fn terminal_size(fd: RawFd) -> (u16, u16) {
41    unsafe {
42        let mut ws: libc::winsize = std::mem::zeroed();
43        if libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) == 0 {
44            (ws.ws_row, ws.ws_col)
45        } else {
46            (24, 80)
47        }
48    }
49}
50
51/// Read bytes from a raw file descriptor.
52/// Returns the number of bytes read, or 0 on EOF/error.
53pub fn read_raw(fd: RawFd, buf: &mut [u8]) -> usize {
54    unsafe {
55        let n = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len());
56        if n > 0 {
57            n as usize
58        } else {
59            0
60        }
61    }
62}
63
64// --- epoll + signalfd-based stdin relay ---
65
66/// Events returned by `StdinRelay::wait()`.
67pub enum StdinEvent {
68    /// stdin has data available to read.
69    Ready,
70    /// Terminal was resized (SIGWINCH).
71    Resize,
72    /// Shutdown was signaled by the other thread.
73    Shutdown,
74}
75
76/// Write-end of the shutdown pipe.
77pub struct ShutdownSignal {
78    pipe_write: RawFd,
79}
80
81unsafe impl Send for ShutdownSignal {}
82
83impl ShutdownSignal {
84    pub fn signal(&self) {
85        unsafe {
86            libc::write(self.pipe_write, [1u8].as_ptr() as *const libc::c_void, 1);
87        }
88    }
89}
90
91impl Drop for ShutdownSignal {
92    fn drop(&mut self) {
93        unsafe {
94            libc::close(self.pipe_write);
95        }
96    }
97}
98
99// Event source identifiers stored in epoll_event.u64
100const EV_STDIN: u64 = 0;
101const EV_PIPE: u64 = 1;
102const EV_SIGNAL: u64 = 2;
103
104/// epoll-based event multiplexer for stdin, SIGWINCH, and shutdown.
105/// Linux equivalent of macOS kqueue-based StdinRelay.
106pub struct StdinRelay {
107    epoll_fd: RawFd,
108    signal_fd: RawFd,
109    pipe_read: RawFd,
110}
111
112impl StdinRelay {
113    /// Create a new relay watching the given stdin fd.
114    pub fn new(stdin_fd: RawFd) -> Option<(StdinRelay, ShutdownSignal)> {
115        unsafe {
116            // Create shutdown pipe
117            let mut fds = [0i32; 2];
118            if libc::pipe(fds.as_mut_ptr()) != 0 {
119                return None;
120            }
121            let pipe_read = fds[0];
122            let pipe_write = fds[1];
123
124            // Create epoll instance
125            let epoll_fd = libc::epoll_create1(0);
126            if epoll_fd < 0 {
127                libc::close(pipe_read);
128                libc::close(pipe_write);
129                return None;
130            }
131
132            // Register stdin
133            let mut ev = libc::epoll_event {
134                events: libc::EPOLLIN as u32,
135                u64: EV_STDIN,
136            };
137            if libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, stdin_fd, &mut ev) < 0 {
138                libc::close(epoll_fd);
139                libc::close(pipe_read);
140                libc::close(pipe_write);
141                return None;
142            }
143
144            // Register shutdown pipe
145            let mut ev = libc::epoll_event {
146                events: libc::EPOLLIN as u32,
147                u64: EV_PIPE,
148            };
149            libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, pipe_read, &mut ev);
150
151            // Create signalfd for SIGWINCH
152            let mut sigset: libc::sigset_t = std::mem::zeroed();
153            libc::sigemptyset(&mut sigset);
154            libc::sigaddset(&mut sigset, libc::SIGWINCH);
155            // Block SIGWINCH so signalfd receives it
156            libc::sigprocmask(libc::SIG_BLOCK, &sigset, std::ptr::null_mut());
157
158            let signal_fd = libc::signalfd(-1, &sigset, libc::SFD_NONBLOCK);
159            if signal_fd < 0 {
160                // signalfd not available — proceed without resize support
161                libc::close(epoll_fd);
162                libc::close(pipe_read);
163                libc::close(pipe_write);
164                return None;
165            }
166
167            let mut ev = libc::epoll_event {
168                events: libc::EPOLLIN as u32,
169                u64: EV_SIGNAL,
170            };
171            libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, signal_fd, &mut ev);
172
173            Some((
174                StdinRelay {
175                    epoll_fd,
176                    signal_fd,
177                    pipe_read,
178                },
179                ShutdownSignal { pipe_write },
180            ))
181        }
182    }
183
184    /// Block until stdin is readable, a resize signal arrives, or shutdown
185    /// is signaled.
186    pub fn wait(&self) -> StdinEvent {
187        unsafe {
188            let mut events = [libc::epoll_event { events: 0, u64: 0 }; 4];
189
190            let n = libc::epoll_wait(self.epoll_fd, events.as_mut_ptr(), 4, -1);
191            if n < 1 {
192                return StdinEvent::Shutdown;
193            }
194
195            for event in events.iter().take(n as usize) {
196                match event.u64 {
197                    EV_STDIN => return StdinEvent::Ready,
198                    EV_PIPE => return StdinEvent::Shutdown,
199                    EV_SIGNAL => {
200                        // Drain the signalfd
201                        let mut info: libc::signalfd_siginfo = std::mem::zeroed();
202                        libc::read(
203                            self.signal_fd,
204                            &mut info as *mut _ as *mut libc::c_void,
205                            std::mem::size_of::<libc::signalfd_siginfo>(),
206                        );
207                        return StdinEvent::Resize;
208                    }
209                    _ => {}
210                }
211            }
212
213            StdinEvent::Shutdown
214        }
215    }
216}
217
218impl Drop for StdinRelay {
219    fn drop(&mut self) {
220        unsafe {
221            libc::close(self.epoll_fd);
222            libc::close(self.signal_fd);
223            libc::close(self.pipe_read);
224            // Unblock SIGWINCH
225            let mut sigset: libc::sigset_t = std::mem::zeroed();
226            libc::sigemptyset(&mut sigset);
227            libc::sigaddset(&mut sigset, libc::SIGWINCH);
228            libc::sigprocmask(libc::SIG_UNBLOCK, &sigset, std::ptr::null_mut());
229        }
230    }
231}