Skip to main content

line_discipline/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use derivative::Derivative;
6use starnix_uapi::errors::Errno;
7use starnix_uapi::signals::{SIGINT, SIGQUIT, SIGTSTP, Signal};
8use starnix_uapi::vfs::FdEvents;
9use starnix_uapi::{
10    CREAD, CS8, CSIZE, ECHO, ECHOCTL, ECHOE, ECHOK, ECHOKE, ECHONL, ECHOPRT, EXTPROC, ICANON,
11    ICRNL, IEXTEN, IGNCR, INLCR, ISIG, ISTRIP, IUCLC, IUTF8, IXANY, IXON, NOFLSH, OCRNL, OLCUC,
12    ONLCR, ONLRET, ONOCR, OPOST, PARENB, PARMRK, TABDLY, VEOF, VEOL, VEOL2, VERASE, VINTR, VKILL,
13    VLNEXT, VQUIT, VREPRINT, VSTART, VSTOP, VSUSP, VWERASE, XTABS, cc_t, error, tcflag_t, uapi,
14};
15use std::collections::VecDeque;
16
17// CANON_MAX_BYTES is the number of bytes that fit into a single line of
18// terminal input in canonical mode. See https://github.com/google/gvisor/blob/master/pkg/sentry/fs/tty/line_discipline.go
19const CANON_MAX_BYTES: usize = 4096;
20
21// NON_CANON_MAX_BYTES is the maximum number of bytes that can be read at
22// a time in non canonical mode.
23const NON_CANON_MAX_BYTES: usize = CANON_MAX_BYTES - 1;
24
25// WAIT_BUFFER_MAX_BYTES is the maximum size of a wait buffer. It is based on
26// https://github.com/google/gvisor/blob/master/pkg/sentry/fsimpl/devpts/queue.go
27const WAIT_BUFFER_MAX_BYTES: usize = 131072;
28
29const SPACES_PER_TAB: usize = 8;
30
31// DISABLED_CHAR is used to indicate that a control character is disabled.
32const DISABLED_CHAR: u8 = 0;
33
34const BACKSPACE_CHAR: u8 = 8; // \b
35
36/// The offset in ASCII between a control character and it's character name.
37/// For example, typing CTRL-C on a keyboard generates the value
38/// b'C' - CONTROL_OFFSET
39const CONTROL_OFFSET: u8 = 0x40;
40
41#[derive(Derivative)]
42#[derivative(Default)]
43#[derivative(Debug)]
44pub struct LineDiscipline {
45    /// |true| is the terminal is locked.
46    #[derivative(Default(value = "true"))]
47    pub locked: bool,
48
49    /// |true| if the output is stopped (due to IXON).
50    #[derivative(Default(value = "false"))]
51    pub stopped: bool,
52
53    /// Terminal size.
54    pub window_size: uapi::winsize,
55
56    /// Terminal configuration.
57    #[derivative(Default(value = "get_default_termios()"))]
58    termios: uapi::termios2,
59
60    /// True if the terminal is currently in the middle of an erase sequence (ECHOPRT).
61    #[derivative(Default(value = "false"))]
62    erasing: bool,
63
64    /// True if the next character should be treated literally.
65    #[derivative(Default(value = "false"))]
66    lnext: bool,
67
68    /// Location in a row of the cursor. Needed to handle certain special characters like
69    /// backspace.
70    column: usize,
71
72    /// Column where the current canonical input line started.
73    canon_column: usize,
74
75    /// Echoes generated while processing input. Flushed when output is not stopped.
76    pending_echoes: Vec<EchoOp>,
77
78    /// Packet mode state (TIOCPKT).
79    #[derivative(Default(value = "false"))]
80    packet_mode_enabled: bool,
81
82    /// Packet mode pending events.
83    #[derivative(Default(value = "0"))]
84    packet_mode_pending_events: u8,
85
86    /// The number of active references to the main part of the terminal. Starts as `None`. The
87    /// main part of the terminal is considered closed when this is `Some(0)`.
88    main_references: Option<u32>,
89
90    /// The number of active references to the replica part of the terminal. Starts as `None`. The
91    /// replica part of the terminal is considered closed when this is `Some(0)`.
92    replica_references: Option<u32>,
93
94    /// Input queue of the terminal. Data flow from the main side to the replica side.
95    #[derivative(Default(value = "Queue::input_queue()"))]
96    input_queue: Option<Queue>,
97
98    /// Output queue of the terminal. Data flow from the replica side to the main side.
99    #[derivative(Default(value = "Queue::output_queue()"))]
100    output_queue: Option<Queue>,
101}
102
103/// Helper trait for input/output buffers.
104pub trait InputBuffer {
105    fn available(&self) -> usize;
106    fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno>;
107}
108
109pub trait OutputBuffer {
110    fn available(&self) -> usize;
111    fn write(&mut self, data: &[u8]) -> Result<usize, Errno>;
112}
113
114/// Macro to help working with the terminal queues.
115macro_rules! with_queue {
116    ($self_:tt . $name:ident . $fn:ident ( $($param:expr),*$(,)?)) => {
117        {
118        let mut queue = $self_.$name . take().unwrap();
119        let result = queue.$fn( $($param),* );
120        $self_.$name = Some(queue);
121        result
122        }
123    };
124}
125
126/// Keep track of the signals to send when handling terminal content.
127#[derive(Debug, PartialEq)]
128#[must_use]
129pub struct PendingSignals {
130    signals: Vec<Signal>,
131}
132
133impl PendingSignals {
134    pub fn new() -> Self {
135        Self { signals: vec![] }
136    }
137
138    /// Add the given signal to the list of signal to send to the associate process group.
139    fn add(&mut self, signal: Signal) {
140        self.signals.push(signal);
141    }
142
143    /// Append all pending signals in `other` to `self`.
144    fn append(&mut self, mut other: Self) {
145        self.signals.append(&mut other.signals);
146    }
147
148    /// Returns a slice of the pending signals.
149    pub fn signals(&self) -> &[Signal] {
150        &self.signals[..]
151    }
152}
153
154/// Represents the type of erase operation that can be performed on terminal input.
155#[derive(Debug, PartialEq)]
156enum EraseType {
157    /// Erase a single character (typically triggered by backspace)
158    Character,
159    /// Erase a word (typically triggered by Ctrl+W)
160    Word,
161    /// Erase the entire line (typically triggered by Ctrl+U)
162    Line,
163}
164
165/// Identifies one of the two ends of a terminal.
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum TerminalSide {
168    Main,
169    Replica,
170}
171
172impl From<bool> for TerminalSide {
173    fn from(is_main: bool) -> Self {
174        if is_main { Self::Main } else { Self::Replica }
175    }
176}
177
178/// Represents a deferred echo operation queued during input processing and executed in
179/// `commit_echoes` when output is not stopped.
180#[derive(Debug, Clone, Copy, PartialEq)]
181enum EchoOp {
182    /// Output a single raw echo byte through `do_output_char`.
183    Byte(RawByte),
184    /// Record the current cursor `column` as `canon_column` (start of canonical input line).
185    SetCanonColumn,
186    /// Erase a tab character (`ECHOE`), computing the number of backspaces from `num_chars` and
187    /// `canon_column` (if `!after_tab`).
188    EraseTab { num_chars: usize, after_tab: bool },
189}
190
191impl LineDiscipline {
192    /// Returns the terminal configuration.
193    pub fn termios(&self) -> &uapi::termios2 {
194        &self.termios
195    }
196
197    fn is_canon_enabled(&self) -> bool {
198        self.termios.has_local_flags(ICANON) && !self.is_extproc_enabled()
199    }
200
201    fn is_extproc_enabled(&self) -> bool {
202        self.termios.has_local_flags(EXTPROC)
203    }
204
205    pub fn is_packet_mode_enabled(&self) -> bool {
206        self.packet_mode_enabled
207    }
208
209    pub fn set_packet_mode(&mut self, enabled: bool) {
210        self.packet_mode_enabled = enabled;
211        if !enabled {
212            self.packet_mode_pending_events = 0;
213        }
214    }
215
216    /// Returns the number of available bytes to read from `side`.
217    pub fn get_available_read_size(&self, side: TerminalSide) -> usize {
218        let queue = match side {
219            TerminalSide::Main => self.output_queue(),
220            TerminalSide::Replica => self.input_queue(),
221        };
222        queue.readable_size()
223    }
224
225    /// Resets transient terminal input state (`erasing`, `lnext`, and `pending_echoes`).
226    fn reset_input_state(&mut self) {
227        self.erasing = false;
228        self.lnext = false;
229        self.pending_echoes.clear();
230    }
231
232    /// Sets the terminal configuration.
233    pub fn set_termios(&mut self, mut termios: uapi::termios2) -> PendingSignals {
234        // Pseudo-terminals always force CS8 | CREAD and clear PARENB.
235        termios.c_cflag &= !(CSIZE | PARENB);
236        termios.c_cflag |= CS8 | CREAD;
237
238        let old_ixon = self.termios.has_input_flags(IXON);
239        let new_ixon = termios.has_input_flags(IXON);
240        let old_flow = old_ixon
241            && self.termios.c_cc[VSTOP as usize] == get_control_character('S')
242            && self.termios.c_cc[VSTART as usize] == get_control_character('Q');
243        let new_flow = new_ixon
244            && termios.c_cc[VSTOP as usize] == get_control_character('S')
245            && termios.c_cc[VSTART as usize] == get_control_character('Q');
246        let extproc_active = ((self.termios.c_lflag | termios.c_lflag) & EXTPROC) != 0;
247        let canon_or_extproc_changed =
248            ((self.termios.c_lflag ^ termios.c_lflag) & (ICANON | EXTPROC)) != 0;
249
250        self.termios = termios;
251
252        if self.packet_mode_enabled {
253            if old_flow != new_flow {
254                self.packet_mode_pending_events &=
255                    !((uapi::TIOCPKT_DOSTOP | uapi::TIOCPKT_NOSTOP) as u8);
256                let event = if new_flow { uapi::TIOCPKT_DOSTOP } else { uapi::TIOCPKT_NOSTOP };
257                self.packet_mode_pending_events |= event as u8;
258            }
259            if extproc_active {
260                self.packet_mode_pending_events |= uapi::TIOCPKT_IOCTL as u8;
261            }
262        }
263
264        if old_ixon && !new_ixon {
265            self.start_tty();
266        }
267
268        if canon_or_extproc_changed {
269            self.erasing = false;
270            self.lnext = false;
271            with_queue!(self.input_queue.on_canon_mode_changed(self))
272        } else {
273            PendingSignals::new()
274        }
275    }
276
277    /// Flushes queues according to `queue_selector` (TCIFLUSH, TCOFLUSH, TCIOFLUSH).
278    pub fn flush(&mut self, side: TerminalSide, queue_selector: u32) -> Result<(), Errno> {
279        let flush_terminal_input = side == TerminalSide::Replica
280            && matches!(queue_selector, uapi::TCIFLUSH | uapi::TCIOFLUSH);
281
282        // We can receive a flush request from either the main or the replica which switch what the
283        // input and output queues are referring to.
284        let (input_queue, output_queue) = match side {
285            TerminalSide::Main => {
286                (self.output_queue.as_mut().unwrap(), self.input_queue.as_mut().unwrap())
287            }
288            TerminalSide::Replica => {
289                (self.input_queue.as_mut().unwrap(), self.output_queue.as_mut().unwrap())
290            }
291        };
292
293        let event;
294        // For input flushes, we discard all data in the pipeline. Data that's already been
295        // delivered to us, and data that's waiting to be delivered.
296        //
297        // For output flushes, we want to only discard data that has been sent, but not yet
298        // delivered to the other side's read_queue. Since this we're a pty and sending is just a
299        // memcpy rather than actually going across a wire, the only time this will happen when the
300        // read_queue fills up and there is backpressure due to full buffers.
301        match queue_selector {
302            uapi::TCIFLUSH => {
303                input_queue.flush();
304                event = uapi::TIOCPKT_FLUSHREAD;
305            }
306            uapi::TCOFLUSH => {
307                output_queue.flush_unprocessed();
308                event = uapi::TIOCPKT_FLUSHWRITE;
309            }
310            uapi::TCIOFLUSH => {
311                input_queue.flush();
312                output_queue.flush_unprocessed();
313                event = uapi::TIOCPKT_FLUSHREAD | uapi::TIOCPKT_FLUSHWRITE;
314            }
315            _ => return error!(EINVAL),
316        };
317
318        if flush_terminal_input {
319            self.reset_input_state();
320        }
321
322        if side == TerminalSide::Replica && self.packet_mode_enabled {
323            self.packet_mode_pending_events |= event as u8;
324        }
325
326        Ok(())
327    }
328
329    /// `close` implementation of the main side of the terminal.
330    pub fn main_close(&mut self) {
331        self.main_references = self.main_references.map(|v| v - 1);
332        if self.is_main_closed() {
333            let _ = self.flush(TerminalSide::Replica, uapi::TCIFLUSH);
334            let _ = self.flush(TerminalSide::Main, uapi::TCIFLUSH);
335        }
336    }
337
338    /// Called when a new reference to the main side of this terminal is made.
339    pub fn main_open(&mut self) {
340        self.main_references = Some(self.main_references.unwrap_or(0) + 1);
341    }
342
343    pub fn is_main_closed(&self) -> bool {
344        matches!(self.main_references, Some(0))
345    }
346
347    /// `query_events` implementation of the main side of the terminal.
348    pub fn main_query_events(&self) -> FdEvents {
349        let mut events =
350            self.output_queue().read_readiness() | self.input_queue().write_readiness();
351        if self.packet_mode_enabled && self.packet_mode_pending_events != 0 {
352            events |= FdEvents::POLLIN | FdEvents::POLLPRI;
353        }
354        if self.is_replica_closed() {
355            events |= FdEvents::POLLHUP;
356        }
357        events
358    }
359
360    /// `read` implementation of the main side of the terminal.
361    pub fn main_read(&mut self, data: &mut dyn OutputBuffer) -> Result<usize, Errno> {
362        if self.packet_mode_enabled && self.packet_mode_pending_events != 0 {
363            if data.available() == 0 {
364                return Ok(0);
365            }
366            let event = self.packet_mode_pending_events;
367            let written = data.write(&[event])?;
368            if written > 0 {
369                self.packet_mode_pending_events = 0;
370            }
371            return Ok(written);
372        }
373        if self.is_replica_closed() && self.output_queue().readable_size() == 0 {
374            return error!(EIO);
375        }
376        if self.packet_mode_enabled {
377            if self.output_queue().readable_size() == 0 {
378                return error!(EAGAIN);
379            }
380            if data.available() == 0 {
381                return Ok(0);
382            }
383            let written = data.write(&[0])?;
384            if written == 0 {
385                return Ok(0);
386            }
387            let res = with_queue!(self.output_queue.read(self, data));
388            match res {
389                Ok((n, signals)) => {
390                    assert!(signals.signals().is_empty());
391                    return Ok(n + 1);
392                }
393                Err(_) => return Ok(1),
394            }
395        }
396        let (n, signals) = with_queue!(self.output_queue.read(self, data))?;
397        assert!(signals.signals().is_empty());
398        Ok(n)
399    }
400
401    /// `write` implementation of the main side of the terminal.
402    ///
403    /// Returns the number of bytes written and any signals generated while processing the input.
404    pub fn main_write(
405        &mut self,
406        data: &mut dyn InputBuffer,
407    ) -> Result<(usize, PendingSignals), Errno> {
408        with_queue!(self.input_queue.write(self, data))
409    }
410
411    /// `close` implementation of the replica side of the terminal.
412    pub fn replica_close(&mut self) {
413        self.replica_references = self.replica_references.map(|v| v - 1);
414    }
415
416    /// Called when a new reference to the replica side of this terminal is made.
417    pub fn replica_open(&mut self) {
418        self.replica_references = Some(self.replica_references.unwrap_or(0) + 1);
419    }
420
421    pub fn is_replica_closed(&self) -> bool {
422        matches!(self.replica_references, Some(0))
423    }
424
425    /// `query_events` implementation of the replica side of the terminal.
426    pub fn replica_query_events(&self) -> FdEvents {
427        if self.is_main_closed() {
428            return FdEvents::POLLIN | FdEvents::POLLOUT | FdEvents::POLLERR | FdEvents::POLLHUP;
429        }
430        let mut events = self.input_queue().read_readiness();
431        if !(self.stopped && self.termios.has_input_flags(IXON)) {
432            events |= self.output_queue().write_readiness();
433        }
434        events
435    }
436
437    /// `read` implementation of the replica side of the terminal.
438    ///
439    /// Returns the number of bytes read and any signals generated while draining waiting input
440    /// buffers.
441    pub fn replica_read(
442        &mut self,
443        data: &mut dyn OutputBuffer,
444    ) -> Result<(usize, PendingSignals), Errno> {
445        if self.is_main_closed() {
446            return Ok((0, PendingSignals::new()));
447        }
448        with_queue!(self.input_queue.read(self, data))
449    }
450
451    /// `write` implementation of the replica side of the terminal.
452    pub fn replica_write(&mut self, data: &mut dyn InputBuffer) -> Result<usize, Errno> {
453        if self.is_main_closed() {
454            return error!(EIO);
455        }
456        if self.stopped && self.termios.has_input_flags(IXON) {
457            return error!(EAGAIN);
458        }
459        let (read_from_userspace, signals) = with_queue!(self.output_queue.write(self, data))?;
460        // Writing to the replica side never generates signals.
461        assert!(signals.signals().is_empty());
462        Ok(read_from_userspace)
463    }
464
465    /// Returns the input queue.
466    fn input_queue(&self) -> &Queue {
467        self.input_queue.as_ref().unwrap()
468    }
469
470    /// Returns the output_queue. The Option is always filled.
471    fn output_queue(&self) -> &Queue {
472        self.output_queue.as_ref().unwrap()
473    }
474
475    /// Return whether a signal must be send when receiving `byte`, and if yes, which.
476    fn handle_signals(&mut self, byte: RawByte) -> Option<Signal> {
477        if !self.termios.has_local_flags(ISIG) {
478            return None;
479        }
480        self.termios.signal(byte)
481    }
482
483    fn stop_tty(&mut self) {
484        if !self.stopped {
485            self.stopped = true;
486            if self.packet_mode_enabled {
487                self.packet_mode_pending_events &= !(uapi::TIOCPKT_START as u8);
488                self.packet_mode_pending_events |= uapi::TIOCPKT_STOP as u8;
489            }
490        }
491    }
492
493    fn start_tty(&mut self) {
494        if self.stopped {
495            self.stopped = false;
496            if self.packet_mode_enabled {
497                self.packet_mode_pending_events &= !(uapi::TIOCPKT_STOP as u8);
498                self.packet_mode_pending_events |= uapi::TIOCPKT_START as u8;
499            }
500            self.commit_echoes();
501            let signals = with_queue!(self.output_queue.drain_waiting_buffer(self));
502            assert!(signals.signals().is_empty());
503        }
504    }
505
506    fn do_output_char(&mut self, mut c: RawByte, out: &mut Vec<RawByte>) {
507        if !self.termios.has_output_flags(OPOST) {
508            out.push(c);
509            return;
510        }
511        match c {
512            b'\n' => {
513                if self.termios.has_output_flags(ONLRET) {
514                    self.column = 0;
515                }
516                if self.termios.has_output_flags(ONLCR) {
517                    self.canon_column = 0;
518                    self.column = 0;
519                    out.extend_from_slice(b"\r\n");
520                    return;
521                }
522                self.canon_column = self.column;
523            }
524            b'\r' => {
525                if self.termios.has_output_flags(ONOCR) && self.column == 0 {
526                    return;
527                }
528                if self.termios.has_output_flags(OCRNL) {
529                    c = b'\n';
530                    if self.termios.has_output_flags(ONLRET) {
531                        self.canon_column = 0;
532                        self.column = 0;
533                    }
534                } else {
535                    self.canon_column = 0;
536                    self.column = 0;
537                }
538            }
539            b'\t' => {
540                let spaces = SPACES_PER_TAB - (self.column % SPACES_PER_TAB);
541                self.column += spaces;
542                if self.termios.c_oflag & TABDLY == XTABS {
543                    out.extend(std::iter::repeat_n(b' ', spaces));
544                    return;
545                }
546            }
547            BACKSPACE_CHAR => {
548                if self.column > 0 {
549                    self.column -= 1;
550                }
551            }
552            _ => {
553                if !is_cntrl(c) {
554                    if self.termios.has_output_flags(OLCUC) {
555                        c.make_ascii_uppercase();
556                    }
557                    if !is_utf8_continuation(c, &self.termios) {
558                        self.column += 1;
559                    }
560                }
561            }
562        }
563        out.push(c);
564    }
565
566    fn echo_raw_byte(&mut self, c: RawByte) {
567        self.pending_echoes.push(EchoOp::Byte(c));
568    }
569
570    fn echo_set_canon_col(&mut self) {
571        self.pending_echoes.push(EchoOp::SetCanonColumn);
572    }
573
574    fn echo_char(&mut self, c: RawByte) {
575        if self.termios.has_local_flags(ECHOCTL) && is_cntrl(c) && c != b'\t' {
576            self.echo_raw_byte(b'^');
577            self.echo_raw_byte(c ^ CONTROL_OFFSET);
578        } else {
579            self.echo_raw_byte(c);
580        }
581    }
582
583    fn finish_erasing(&mut self) {
584        if self.erasing {
585            self.echo_raw_byte(b'/');
586            self.erasing = false;
587        }
588    }
589
590    fn commit_echoes(&mut self) {
591        if self.stopped && self.termios.has_input_flags(IXON) {
592            return;
593        }
594        if !self.pending_echoes.is_empty() {
595            let ops = std::mem::take(&mut self.pending_echoes);
596            let mut echoes = Vec::with_capacity(ops.len());
597            for op in ops {
598                match op {
599                    EchoOp::Byte(c) => self.do_output_char(c, &mut echoes),
600                    EchoOp::SetCanonColumn => {
601                        self.canon_column = self.column;
602                    }
603                    EchoOp::EraseTab { mut num_chars, after_tab } => {
604                        if !after_tab {
605                            num_chars += self.canon_column;
606                        }
607                        let num_bs = SPACES_PER_TAB - (num_chars % SPACES_PER_TAB);
608                        for _ in 0..num_bs {
609                            self.do_output_char(BACKSPACE_CHAR, &mut echoes);
610                        }
611                    }
612                }
613            }
614            if !echoes.is_empty() {
615                if let Some(ref mut output_queue) = self.output_queue {
616                    output_queue.read_queue.push_back(ReadPacket { data: echoes, has_eof: false });
617                }
618            }
619        }
620    }
621
622    fn eraser(&mut self, queue: &mut Queue, c: RawByte) {
623        if queue.line_buffer.is_empty() {
624            return;
625        }
626
627        let erase_type = if self.termios.is_erase(c) {
628            EraseType::Character
629        } else if self.termios.is_werase(c) {
630            EraseType::Word
631        } else {
632            if !self.termios.has_local_flags(ECHO) {
633                queue.line_buffer.clear();
634                return;
635            }
636            if !self.termios.has_local_flags(ECHOK)
637                || !self.termios.has_local_flags(ECHOKE)
638                || !self.termios.has_local_flags(ECHOE)
639            {
640                queue.line_buffer.clear();
641                self.finish_erasing();
642                self.echo_char(c);
643                if self.termios.has_local_flags(ECHOK) {
644                    self.echo_raw_byte(b'\n');
645                }
646                return;
647            }
648            EraseType::Line
649        };
650
651        let mut seen_alnums = 0;
652        while !queue.line_buffer.is_empty() {
653            let mut pos = queue.line_buffer.len();
654            while pos > 0 {
655                pos -= 1;
656                if !is_utf8_continuation(queue.line_buffer[pos], &self.termios) {
657                    break;
658                }
659            }
660            let first_byte = queue.line_buffer[pos];
661            if is_utf8_continuation(first_byte, &self.termios) {
662                // Do not partially erase an incomplete/stray UTF-8 continuation sequence.
663                break;
664            }
665            if erase_type == EraseType::Word {
666                if is_linux_alnum_or_underscore(first_byte) {
667                    seen_alnums += 1;
668                } else if seen_alnums > 0 {
669                    break;
670                }
671            }
672            let erased_char: Vec<RawByte> = queue.line_buffer.drain(pos..).collect();
673            if self.termios.has_local_flags(ECHO) {
674                if self.termios.has_local_flags(ECHOPRT) {
675                    if !self.erasing {
676                        self.echo_raw_byte(b'\\');
677                        self.erasing = true;
678                    }
679                    for &b in &erased_char {
680                        self.echo_char(b);
681                    }
682                } else if erase_type == EraseType::Character && !self.termios.has_local_flags(ECHOE)
683                {
684                    self.echo_char(self.termios.c_cc[VERASE as usize]);
685                } else if first_byte == b'\t' {
686                    let mut num_chars = 0;
687                    let mut after_tab = false;
688                    for &b in queue.line_buffer.iter().rev() {
689                        if b == b'\t' {
690                            after_tab = true;
691                            break;
692                        } else if is_cntrl(b) {
693                            if self.termios.has_local_flags(ECHOCTL) {
694                                num_chars += 2;
695                            }
696                        } else if !is_utf8_continuation(b, &self.termios) {
697                            num_chars += 1;
698                        }
699                    }
700                    self.pending_echoes.push(EchoOp::EraseTab { num_chars, after_tab });
701                } else {
702                    if is_cntrl(first_byte) && self.termios.has_local_flags(ECHOCTL) {
703                        self.echo_raw_byte(BACKSPACE_CHAR);
704                        self.echo_raw_byte(b' ');
705                        self.echo_raw_byte(BACKSPACE_CHAR);
706                    }
707                    if !is_cntrl(first_byte) || self.termios.has_local_flags(ECHOCTL) {
708                        self.echo_raw_byte(BACKSPACE_CHAR);
709                        self.echo_raw_byte(b' ');
710                        self.echo_raw_byte(BACKSPACE_CHAR);
711                    }
712                }
713            }
714            if erase_type == EraseType::Character {
715                break;
716            }
717        }
718        if queue.line_buffer.is_empty() && self.termios.has_local_flags(ECHO) {
719            self.finish_erasing();
720        }
721    }
722
723    fn transform(
724        &mut self,
725        is_input: bool,
726        queue: &mut Queue,
727        buffer: &[RawByte],
728    ) -> (usize, PendingSignals) {
729        if is_input {
730            self.transform_input(queue, buffer)
731        } else {
732            (self.transform_output(queue, buffer), PendingSignals::new())
733        }
734    }
735
736    fn transform_output(&mut self, queue: &mut Queue, original_buffer: &[RawByte]) -> usize {
737        if self.stopped && self.termios.has_input_flags(IXON) {
738            return 0;
739        }
740        let mut buffer = original_buffer;
741
742        // transform_output is effectively always in noncanonical mode, as the
743        // main termios never has ICANON set.
744
745        if !self.termios.has_output_flags(OPOST) {
746            let limit = CANON_MAX_BYTES.saturating_sub(queue.readable_size());
747            if limit == 0 {
748                return 0;
749            }
750            let to_write = std::cmp::min(limit, buffer.len());
751            queue
752                .read_queue
753                .push_back(ReadPacket { data: buffer[..to_write].to_vec(), has_eof: false });
754            return to_write;
755        }
756
757        let mut return_value = 0;
758        while !buffer.is_empty()
759            && queue.readable_size() + queue.line_buffer.len() < CANON_MAX_BYTES
760        {
761            let c = buffer[0];
762            return_value += 1;
763            buffer = &buffer[1..];
764            self.do_output_char(c, &mut queue.line_buffer);
765        }
766        if !queue.line_buffer.is_empty() {
767            queue.flush_line_buffer();
768        }
769        return_value
770    }
771
772    fn transform_input(
773        &mut self,
774        queue: &mut Queue,
775        original_buffer: &[RawByte],
776    ) -> (usize, PendingSignals) {
777        let mut buffer = original_buffer;
778
779        let mut return_value = 0;
780        let mut signals = PendingSignals::new();
781        while !buffer.is_empty()
782            && queue.buffer_len()
783                < if self.is_canon_enabled() && queue.read_queue.is_empty() {
784                    CANON_MAX_BYTES
785                } else {
786                    NON_CANON_MAX_BYTES
787                }
788        {
789            let mut c = buffer[0];
790
791            if self.termios.has_input_flags(ISTRIP) {
792                c &= 0x7f;
793            }
794            if self.termios.has_input_flags(IUCLC) && self.termios.has_local_flags(IEXTEN) {
795                c.make_ascii_lowercase();
796            }
797
798            // Step 1: Handle literal-next (VLNEXT) active from previous character.
799            if self.lnext {
800                self.lnext = false;
801                if self.stopped
802                    && self.termios.has_input_flags(IXON)
803                    && self.termios.has_input_flags(IXANY)
804                {
805                    self.start_tty();
806                }
807                let parmrk_double = c == 0xff && self.termios.has_input_flags(PARMRK);
808                let pushed_len = if parmrk_double { 2 } else { 1 };
809                if queue.buffer_len() + pushed_len > NON_CANON_MAX_BYTES {
810                    if self.is_canon_enabled() && queue.read_queue.is_empty() {
811                        buffer = &buffer[1..];
812                        return_value += 1;
813                        continue;
814                    }
815                    break;
816                }
817                if self.termios.has_local_flags(ECHO) {
818                    self.finish_erasing();
819                    if queue.line_buffer.is_empty() {
820                        self.echo_set_canon_col();
821                    }
822                    self.echo_char(c);
823                }
824                // Note: Linux `n_tty` pushes both `0xff` bytes directly into `read_buf` when
825                // `PARMRK` is enabled (so canonical `eraser` and `VREPRINT` operate on each `0xff`
826                // byte in `line_buffer`).
827                if parmrk_double {
828                    queue.line_buffer.extend_from_slice(&[0xff, 0xff]);
829                } else {
830                    queue.line_buffer.push(c);
831                }
832                buffer = &buffer[1..];
833                return_value += 1;
834                continue;
835            }
836
837            // Step 2: EXTPROC bypasses all special character and echo processing.
838            if self.is_extproc_enabled() {
839                if queue.buffer_len() + 1 > NON_CANON_MAX_BYTES {
840                    break;
841                }
842                queue.line_buffer.push(c);
843                buffer = &buffer[1..];
844                return_value += 1;
845                continue;
846            }
847
848            // Step 3: IXON flow control (VSTART / VSTOP) takes precedence over ISIG and ICANON.
849            if self.termios.has_input_flags(IXON) {
850                if c == self.termios.c_cc[VSTART as usize]
851                    && self.termios.c_cc[VSTART as usize] != DISABLED_CHAR
852                {
853                    self.start_tty();
854                    buffer = &buffer[1..];
855                    return_value += 1;
856                    continue;
857                }
858                if c == self.termios.c_cc[VSTOP as usize]
859                    && self.termios.c_cc[VSTOP as usize] != DISABLED_CHAR
860                {
861                    self.stop_tty();
862                    buffer = &buffer[1..];
863                    return_value += 1;
864                    continue;
865                }
866            }
867
868            // Step 4: ISIG signal characters (VINTR, VQUIT, VSUSP).
869            if let Some(signal) = self.handle_signals(c) {
870                signals.add(signal);
871                if !self.termios.has_local_flags(NOFLSH) {
872                    queue.flush_buffers();
873                    self.reset_input_state();
874                    if let Some(ref mut output_queue) = self.output_queue {
875                        output_queue.flush();
876                    }
877                    if self.packet_mode_enabled {
878                        self.packet_mode_pending_events |=
879                            (uapi::TIOCPKT_FLUSHREAD | uapi::TIOCPKT_FLUSHWRITE) as u8;
880                    }
881                }
882                if self.termios.has_input_flags(IXON) {
883                    self.start_tty();
884                }
885                if self.termios.has_local_flags(ECHO) {
886                    self.echo_char(c);
887                    self.commit_echoes();
888                }
889                buffer = &buffer[1..];
890                return_value += 1;
891                continue;
892            }
893
894            // Step 5: IXANY restarts output on any character not consumed above.
895            if self.stopped
896                && self.termios.has_input_flags(IXON)
897                && self.termios.has_input_flags(IXANY)
898            {
899                self.start_tty();
900            }
901
902            // Step 6: CR/NL input translations (IGNCR, ICRNL, INLCR).
903            match c {
904                b'\r' => {
905                    if self.termios.has_input_flags(IGNCR) {
906                        buffer = &buffer[1..];
907                        return_value += 1;
908                        continue;
909                    }
910                    if self.termios.has_input_flags(ICRNL) {
911                        c = b'\n';
912                    }
913                }
914                b'\n' => {
915                    if self.termios.has_input_flags(INLCR) {
916                        c = b'\r';
917                    }
918                }
919                _ => {}
920            }
921
922            // Step 7: Canonical mode special characters.
923            if self.is_canon_enabled() {
924                if self.termios.is_erase(c) || self.termios.is_kill(c) || self.termios.is_werase(c)
925                {
926                    self.eraser(queue, c);
927                    buffer = &buffer[1..];
928                    return_value += 1;
929                    continue;
930                }
931
932                if self.termios.has_local_flags(IEXTEN) {
933                    if c == self.termios.c_cc[VLNEXT as usize]
934                        && self.termios.c_cc[VLNEXT as usize] != DISABLED_CHAR
935                    {
936                        self.lnext = true;
937                        if self.termios.has_local_flags(ECHO) {
938                            self.finish_erasing();
939                            if self.termios.has_local_flags(ECHOCTL) {
940                                self.echo_raw_byte(b'^');
941                                self.echo_raw_byte(BACKSPACE_CHAR);
942                            }
943                        }
944                        buffer = &buffer[1..];
945                        return_value += 1;
946                        continue;
947                    }
948
949                    if c == self.termios.c_cc[VREPRINT as usize]
950                        && self.termios.c_cc[VREPRINT as usize] != DISABLED_CHAR
951                        && self.termios.has_local_flags(ECHO)
952                    {
953                        self.finish_erasing();
954                        self.echo_char(c);
955                        self.echo_raw_byte(b'\n');
956                        for &b in &queue.line_buffer {
957                            self.echo_char(b);
958                        }
959                        buffer = &buffer[1..];
960                        return_value += 1;
961                        continue;
962                    }
963                }
964
965                if c == b'\n' {
966                    if self.termios.has_local_flags(ECHO) || self.termios.has_local_flags(ECHONL) {
967                        self.echo_raw_byte(b'\n');
968                    }
969                    queue.line_buffer.push(b'\n');
970                    queue.flush_line_buffer();
971                    buffer = &buffer[1..];
972                    return_value += 1;
973                    continue;
974                }
975
976                if self.termios.is_eof(c) {
977                    let data = std::mem::take(&mut queue.line_buffer);
978                    queue.read_queue.push_back(ReadPacket { data, has_eof: true });
979                    buffer = &buffer[1..];
980                    return_value += 1;
981                    continue;
982                }
983
984                if self.termios.is_eol(c) {
985                    let parmrk_double = c == 0xff && self.termios.has_input_flags(PARMRK);
986                    let pushed_len = if parmrk_double { 2 } else { 1 };
987                    let max_bytes = if queue.read_queue.is_empty() {
988                        CANON_MAX_BYTES
989                    } else {
990                        NON_CANON_MAX_BYTES
991                    };
992                    if queue.buffer_len() + pushed_len > max_bytes {
993                        if queue.read_queue.is_empty() {
994                            buffer = &buffer[1..];
995                            return_value += 1;
996                            continue;
997                        }
998                        break;
999                    }
1000                    // Matches Linux `n_tty_receive_char_special`: `EOL_CHAR` / `EOL2_CHAR` (like
1001                    // `\n`) does not call `finish_erasing()`; an open `ECHOPRT` `\.../` sequence is
1002                    // closed by the next normal character (or `VLNEXT` / `VREPRINT`).
1003                    if self.termios.has_local_flags(ECHO) {
1004                        if queue.line_buffer.is_empty() {
1005                            self.echo_set_canon_col();
1006                        }
1007                        self.echo_char(c);
1008                    }
1009                    if parmrk_double {
1010                        queue.line_buffer.extend_from_slice(&[0xff, 0xff]);
1011                    } else {
1012                        queue.line_buffer.push(c);
1013                    }
1014                    queue.flush_line_buffer();
1015                    buffer = &buffer[1..];
1016                    return_value += 1;
1017                    continue;
1018                }
1019            }
1020
1021            // Step 8: Normal character (or \n in non-canonical mode).
1022            let parmrk_double = c == 0xff && self.termios.has_input_flags(PARMRK);
1023            let pushed_len = if parmrk_double { 2 } else { 1 };
1024            if queue.buffer_len() + pushed_len > NON_CANON_MAX_BYTES {
1025                if self.is_canon_enabled() && queue.read_queue.is_empty() {
1026                    buffer = &buffer[1..];
1027                    return_value += 1;
1028                    continue;
1029                }
1030                break;
1031            }
1032
1033            if self.termios.has_local_flags(ECHO) {
1034                self.finish_erasing();
1035                if queue.line_buffer.is_empty() {
1036                    self.echo_set_canon_col();
1037                }
1038                self.echo_char(c);
1039            }
1040
1041            if parmrk_double {
1042                queue.line_buffer.extend_from_slice(&[0xff, 0xff]);
1043            } else {
1044                queue.line_buffer.push(c);
1045            }
1046            buffer = &buffer[1..];
1047            return_value += 1;
1048        }
1049
1050        self.commit_echoes();
1051
1052        // In noncanonical mode (or EXTPROC), everything is immediately readable.
1053        if !self.is_canon_enabled() && !queue.line_buffer.is_empty() {
1054            queue.flush_line_buffer();
1055        }
1056
1057        (return_value, signals)
1058    }
1059}
1060
1061/// Alias used to mark bytes in the queues that have not yet been processed and pushed into the
1062/// read buffer. See `Queue`.
1063type RawByte = u8;
1064
1065#[derive(Debug, Default)]
1066struct ReadPacket {
1067    data: Vec<u8>,
1068    has_eof: bool,
1069}
1070
1071#[derive(Debug, Default)]
1072struct Queue {
1073    /// The queue of data ready to be read. Each element is a "datagram" (line or chunk).
1074    /// Empty byte vectors represent EOF markers (read returns 0).
1075    read_queue: VecDeque<ReadPacket>,
1076
1077    /// The incomplete line/chunk being processed but not yet ready for the read_queue.
1078    /// In Canonical mode, this holds the current line being edited.
1079    /// In Non-Canonical mode, this holds data until it is pushed to the read_queue.
1080    line_buffer: Vec<u8>,
1081
1082    /// Data that can't fit into readBuf. It is put here until it can be loaded into the read
1083    /// buffer. Contains data that hasn't been processed.
1084    wait_buffers: VecDeque<Vec<RawByte>>,
1085
1086    /// The length of the data in `wait_buffers`.
1087    total_wait_buffer_length: usize,
1088
1089    /// Whether this queue in the input queue. Needed to know how to transform received data.
1090    is_input: bool,
1091}
1092
1093impl Queue {
1094    fn output_queue() -> Option<Self> {
1095        Some(Queue { is_input: false, ..Default::default() })
1096    }
1097
1098    fn input_queue() -> Option<Self> {
1099        Some(Queue { is_input: true, ..Default::default() })
1100    }
1101
1102    /// Returns whether the queue is ready to be written to.
1103    fn write_readiness(&self) -> FdEvents {
1104        if self.total_wait_buffer_length < WAIT_BUFFER_MAX_BYTES {
1105            FdEvents::POLLOUT
1106        } else {
1107            FdEvents::empty()
1108        }
1109    }
1110
1111    /// Returns whether the queue is ready to be read from.
1112    fn read_readiness(&self) -> FdEvents {
1113        // If there's an empty "datagram" in read_queue, it means EOF, which is "readable" (returns 0).
1114        if !self.read_queue.is_empty() { FdEvents::POLLIN } else { FdEvents::empty() }
1115    }
1116
1117    /// Returns the number of bytes ready to be read.
1118    fn readable_size(&self) -> usize {
1119        // We sum up everything in the read_queue.
1120        // NOTE: This might over-report if we only return one datagram at a time, but for poll/FIONREAD it's generally answering "how much is there".
1121        self.read_queue.iter().map(|p| p.data.len()).sum()
1122    }
1123
1124    /// Returns the total buffer occupancy in `read_queue` (including 1 byte per `VEOF` marker,
1125    /// matching `__DISABLED_CHAR` in Linux `n_tty`'s `read_buf`) plus `line_buffer`.
1126    fn buffer_len(&self) -> usize {
1127        self.read_queue.iter().map(|p| p.data.len() + usize::from(p.has_eof)).sum::<usize>()
1128            + self.line_buffer.len()
1129    }
1130
1131    /// Read from the queue into `data`. Returns the number of bytes copied and any pending signals
1132    /// generated by draining the wait buffer.
1133    fn read(
1134        &mut self,
1135        terminal: &mut LineDiscipline,
1136        data: &mut dyn OutputBuffer,
1137    ) -> Result<(usize, PendingSignals), Errno> {
1138        if self.read_queue.is_empty() {
1139            return error!(EAGAIN);
1140        }
1141        if data.available() == 0 {
1142            return Ok((0, PendingSignals::new()));
1143        }
1144
1145        let mut total_written = 0;
1146        while let Some(mut packet) = self.read_queue.pop_front() {
1147            if packet.data.is_empty() {
1148                if total_written > 0 {
1149                    // We've already read some data. We need to complete the read with that data and
1150                    // leave the empty datagram in the queue to signal EOF on the next read.
1151                    self.read_queue.push_front(packet);
1152                }
1153                break;
1154            }
1155
1156            match data.write(&packet.data) {
1157                Ok(written) => {
1158                    total_written += written;
1159                    if written < packet.data.len() {
1160                        // Put back the unread part, preserving the EOF marker on the tail.
1161                        let remaining = packet.data.split_off(written);
1162                        self.read_queue
1163                            .push_front(ReadPacket { data: remaining, has_eof: packet.has_eof });
1164                        // Destination full.
1165                        break;
1166                    }
1167
1168                    // If we are in canonical input mode (and not EXTPROC), or this packet was
1169                    // terminated by VEOF, we stop after one packet (one line).
1170                    if (self.is_input && terminal.is_canon_enabled()) || packet.has_eof {
1171                        break;
1172                    }
1173                }
1174                Err(e) => {
1175                    // If write failed, push back the whole packet.
1176                    self.read_queue.push_front(packet);
1177                    if total_written > 0 {
1178                        // If we managed to write something before error, return success.
1179                        let signals = self.drain_waiting_buffer(terminal);
1180                        return Ok((total_written, signals));
1181                    }
1182                    return Err(e);
1183                }
1184            }
1185        }
1186
1187        let signals = self.drain_waiting_buffer(terminal);
1188        Ok((total_written, signals))
1189    }
1190
1191    /// Writes to the queue from `data`. Returns the number of bytes copied.
1192    fn write(
1193        &mut self,
1194        terminal: &mut LineDiscipline,
1195        data: &mut dyn InputBuffer,
1196    ) -> Result<(usize, PendingSignals), Errno> {
1197        let room = WAIT_BUFFER_MAX_BYTES - self.total_wait_buffer_length;
1198        let data_length = data.available();
1199        if room == 0 && data_length > 0 {
1200            return error!(EAGAIN);
1201        }
1202        let buffer = data.read_to_vec_exact(std::cmp::min(room, data_length))?;
1203        let read_from_userspace = buffer.len();
1204        let signals = self.push_to_waiting_buffer(terminal, buffer);
1205        Ok((read_from_userspace, signals))
1206    }
1207
1208    /// Pushes the given buffer into the wait_buffers, and process the wait_buffers.
1209    fn push_to_waiting_buffer(
1210        &mut self,
1211        terminal: &mut LineDiscipline,
1212        buffer: Vec<RawByte>,
1213    ) -> PendingSignals {
1214        self.total_wait_buffer_length += buffer.len();
1215        self.wait_buffers.push_back(buffer);
1216        self.drain_waiting_buffer(terminal)
1217    }
1218
1219    /// Processes the wait_buffers, filling the read buffer.
1220    fn drain_waiting_buffer(&mut self, terminal: &mut LineDiscipline) -> PendingSignals {
1221        let mut signals_to_return = PendingSignals::new();
1222        while let Some(wait_buffer) = self.wait_buffers.pop_front() {
1223            self.total_wait_buffer_length -= wait_buffer.len();
1224            let (count, signals) = terminal.transform(self.is_input, self, &wait_buffer);
1225            signals_to_return.append(signals);
1226            if count != wait_buffer.len() {
1227                let remaining = wait_buffer[count..].to_vec();
1228                self.total_wait_buffer_length += remaining.len();
1229                self.wait_buffers.push_front(remaining);
1230                break;
1231            }
1232        }
1233        signals_to_return
1234    }
1235
1236    /// Flushed the line buffer to the read queue.
1237    fn flush_line_buffer(&mut self) {
1238        self.read_queue
1239            .push_back(ReadPacket { data: std::mem::take(&mut self.line_buffer), has_eof: false });
1240    }
1241
1242    /// Flush the content of the queue.
1243    fn flush(&mut self) {
1244        self.flush_buffers();
1245        self.flush_unprocessed();
1246    }
1247
1248    /// Flush the processed read queue and in-progress line buffer.
1249    fn flush_buffers(&mut self) {
1250        self.read_queue.clear();
1251        self.line_buffer.clear();
1252    }
1253
1254    /// Flush only the part of the queue which has not yet been processed.
1255    fn flush_unprocessed(&mut self) {
1256        self.wait_buffers.clear();
1257        self.total_wait_buffer_length = 0;
1258    }
1259
1260    /// Called when canonical mode or EXTPROC changes on the input queue.
1261    fn on_canon_mode_changed(&mut self, terminal: &mut LineDiscipline) -> PendingSignals {
1262        let mut combined = Vec::new();
1263        for packet in self.read_queue.drain(..) {
1264            combined.extend(packet.data);
1265            if packet.has_eof {
1266                combined.push(DISABLED_CHAR);
1267            }
1268        }
1269        combined.append(&mut self.line_buffer);
1270        if !combined.is_empty() {
1271            let has_eof = terminal.is_canon_enabled() && combined.last() == Some(&DISABLED_CHAR);
1272            if has_eof {
1273                combined.pop();
1274            }
1275            self.read_queue.push_back(ReadPacket { data: combined, has_eof });
1276        }
1277        self.drain_waiting_buffer(terminal)
1278    }
1279}
1280
1281// Helper functions (copied from terminal.rs)
1282// Returns the ASCII representation of the given char. This will assert if the character is not
1283// ascii.
1284fn get_ascii(c: char) -> u8 {
1285    let mut dest: [u8; 1] = [0];
1286    c.encode_utf8(&mut dest);
1287    dest[0]
1288}
1289
1290// Returns the control character associated with the given letter.
1291fn get_control_character(c: char) -> cc_t {
1292    get_ascii(c) - get_ascii('A') + 1
1293}
1294
1295// Returns the default control characters of a terminal.
1296fn get_default_control_characters() -> [cc_t; 19usize] {
1297    [
1298        get_control_character('C'),  // VINTR = ^C
1299        get_control_character('\\'), // VQUIT = ^\
1300        get_ascii('\x7f'),           // VERASE = DEL
1301        get_control_character('U'),  // VKILL = ^U
1302        get_control_character('D'),  // VEOF = ^D
1303        0,                           // VTIME
1304        1,                           // VMIN
1305        0,                           // VSWTC
1306        get_control_character('Q'),  // VSTART = ^Q
1307        get_control_character('S'),  // VSTOP = ^S
1308        get_control_character('Z'),  // VSUSP = ^Z
1309        0,                           // VEOL
1310        get_control_character('R'),  // VREPRINT = ^R
1311        get_control_character('O'),  // VDISCARD = ^O
1312        get_control_character('W'),  // VWERASE = ^W
1313        get_control_character('V'),  // VLNEXT = ^V
1314        0,                           // VEOL2
1315        0,                           // Remaining data in the array,
1316        0,                           // Remaining data in the array,
1317    ]
1318}
1319
1320const DEFAULT_SPEED: u32 = 38400;
1321
1322// Returns the default replica terminal configuration.
1323pub fn get_default_termios() -> uapi::termios2 {
1324    uapi::termios2 {
1325        c_iflag: uapi::ICRNL | uapi::IXON,
1326        c_oflag: uapi::OPOST | uapi::ONLCR,
1327        c_cflag: uapi::B38400 | uapi::CS8 | uapi::CREAD,
1328        c_lflag: uapi::ISIG
1329            | uapi::ICANON
1330            | uapi::ECHO
1331            | uapi::ECHOE
1332            | uapi::ECHOK
1333            | uapi::ECHOCTL
1334            | uapi::ECHOKE
1335            | uapi::IEXTEN,
1336        c_line: 0,
1337        c_cc: get_default_control_characters(),
1338        c_ispeed: DEFAULT_SPEED,
1339        c_ospeed: DEFAULT_SPEED,
1340    }
1341}
1342
1343/// Helper trait for termios to help parse the configuration.
1344trait TermIOS {
1345    fn has_input_flags(&self, flags: tcflag_t) -> bool;
1346    fn has_output_flags(&self, flags: tcflag_t) -> bool;
1347    fn has_local_flags(&self, flags: tcflag_t) -> bool;
1348    fn is_eof(&self, c: RawByte) -> bool;
1349    fn is_erase(&self, c: RawByte) -> bool;
1350    fn is_werase(&self, c: RawByte) -> bool;
1351    fn is_kill(&self, c: RawByte) -> bool;
1352    fn is_eol(&self, c: RawByte) -> bool;
1353    fn signal(&self, c: RawByte) -> Option<Signal>;
1354}
1355
1356impl TermIOS for uapi::termios2 {
1357    fn has_input_flags(&self, flags: tcflag_t) -> bool {
1358        self.c_iflag & flags == flags
1359    }
1360    fn has_output_flags(&self, flags: tcflag_t) -> bool {
1361        self.c_oflag & flags == flags
1362    }
1363    fn has_local_flags(&self, flags: tcflag_t) -> bool {
1364        self.c_lflag & flags == flags
1365    }
1366    fn is_eof(&self, c: RawByte) -> bool {
1367        c == self.c_cc[VEOF as usize] && self.c_cc[VEOF as usize] != DISABLED_CHAR
1368    }
1369    fn is_erase(&self, c: RawByte) -> bool {
1370        c == self.c_cc[VERASE as usize] && self.c_cc[VERASE as usize] != DISABLED_CHAR
1371    }
1372    fn is_werase(&self, c: RawByte) -> bool {
1373        c == self.c_cc[VWERASE as usize]
1374            && self.c_cc[VWERASE as usize] != DISABLED_CHAR
1375            && self.has_local_flags(IEXTEN)
1376    }
1377    fn is_kill(&self, c: RawByte) -> bool {
1378        c == self.c_cc[VKILL as usize] && self.c_cc[VKILL as usize] != DISABLED_CHAR
1379    }
1380    fn is_eol(&self, c: RawByte) -> bool {
1381        if c == DISABLED_CHAR {
1382            return false;
1383        }
1384        if c == self.c_cc[VEOL as usize] {
1385            return true;
1386        }
1387        if c == self.c_cc[VEOL2 as usize] {
1388            return self.has_local_flags(IEXTEN);
1389        }
1390        false
1391    }
1392    fn signal(&self, c: RawByte) -> Option<Signal> {
1393        if c == DISABLED_CHAR {
1394            return None;
1395        }
1396        if c == self.c_cc[VINTR as usize] {
1397            return Some(SIGINT);
1398        }
1399        if c == self.c_cc[VQUIT as usize] {
1400            return Some(SIGQUIT);
1401        }
1402        if c == self.c_cc[VSUSP as usize] {
1403            return Some(SIGTSTP);
1404        }
1405        None
1406    }
1407}
1408
1409fn is_cntrl(c: RawByte) -> bool {
1410    c <= 0x1f || c == 0x7f
1411}
1412
1413fn is_linux_alnum_or_underscore(c: RawByte) -> bool {
1414    c.is_ascii_alphanumeric() || c == b'_' || matches!(c, 0xc0..=0xd6 | 0xd8..=0xf6 | 0xf8..=0xff)
1415}
1416
1417fn is_utf8_continuation(c: RawByte, termios: &uapi::termios2) -> bool {
1418    termios.has_input_flags(IUTF8) && (c & 0xc0) == 0x80
1419}
1420
1421#[cfg(test)]
1422mod tests {
1423    use super::*;
1424
1425    #[::fuchsia::test]
1426    fn test_ascii_conversion() {
1427        assert_eq!(get_ascii(' '), 32);
1428    }
1429
1430    #[::fuchsia::test]
1431    fn test_control_character() {
1432        assert_eq!(get_control_character('C'), 3);
1433    }
1434
1435    #[::fuchsia::test]
1436    fn test_signal_handling_with_disabled_chars() {
1437        let mut termios = get_default_termios();
1438        assert_eq!(termios.signal(3), Some(SIGINT));
1439        assert_eq!(termios.signal(28), Some(SIGQUIT));
1440        assert_eq!(termios.signal(26), Some(SIGTSTP));
1441
1442        termios.c_cc[VINTR as usize] = DISABLED_CHAR;
1443        termios.c_cc[VQUIT as usize] = DISABLED_CHAR;
1444        termios.c_cc[VSUSP as usize] = DISABLED_CHAR;
1445
1446        assert_eq!(termios.signal(0), None);
1447        assert_eq!(termios.signal(3), None); // Normally ^C (SIGINT)
1448        assert_eq!(termios.signal(28), None); // Normally ^\ (SIGQUIT)
1449        assert_eq!(termios.signal(26), None); // Normally ^Z (SIGTSTP)
1450    }
1451
1452    struct TestBuffer {
1453        data: Vec<u8>,
1454    }
1455
1456    impl InputBuffer for TestBuffer {
1457        fn available(&self) -> usize {
1458            self.data.len()
1459        }
1460        fn read_to_vec_exact(&mut self, size: usize) -> Result<Vec<u8>, Errno> {
1461            if size > self.data.len() {
1462                return error!(EAGAIN);
1463            }
1464            Ok(self.data.drain(0..size).collect())
1465        }
1466    }
1467
1468    impl OutputBuffer for TestBuffer {
1469        fn available(&self) -> usize {
1470            usize::MAX
1471        }
1472        fn write(&mut self, data: &[u8]) -> Result<usize, Errno> {
1473            self.data.extend_from_slice(data);
1474            Ok(data.len())
1475        }
1476    }
1477
1478    #[::fuchsia::test]
1479    fn test_flush() {
1480        fn make_ld() -> LineDiscipline {
1481            let mut ld = LineDiscipline::default();
1482            ld.main_open();
1483            ld.replica_open();
1484
1485            let mut termios = get_default_termios();
1486            termios.c_lflag &= !ECHO;
1487            termios.c_oflag &= !OPOST;
1488            let _ = ld.set_termios(termios);
1489
1490            // Write some data from main to the replica.
1491            // This goes to input_queue.
1492            let mut input = TestBuffer { data: b"ping\n".to_vec() };
1493            let (written, signals) = ld.main_write(&mut input).unwrap();
1494            assert_eq!(written, 5);
1495            assert!(signals.signals().is_empty());
1496
1497            // Write some data from replica to main.
1498            // This goes to output_queue.
1499            let mut output = TestBuffer { data: b"pong\n".to_vec() };
1500            let written = ld.replica_write(&mut output).unwrap();
1501            assert_eq!(written, 5);
1502            ld
1503        }
1504
1505        let mut read_buf = TestBuffer { data: vec![] };
1506
1507        // A TCIFLUSH from the main side should flush only the main's input (output_queue)
1508        let mut ld = make_ld();
1509        ld.flush(TerminalSide::Main, uapi::TCIFLUSH).unwrap();
1510        read_buf.data.clear();
1511        assert_eq!(error!(EAGAIN), ld.main_read(&mut read_buf));
1512        assert!(read_buf.data.is_empty());
1513        read_buf.data.clear();
1514        assert!(ld.replica_read(&mut read_buf).is_ok());
1515        assert_eq!(read_buf.data, b"ping\n");
1516
1517        // A TCIFLUSH from the replica side should flush only the replica's input (input_queue)
1518        let mut ld = make_ld();
1519        ld.flush(TerminalSide::Replica, uapi::TCIFLUSH).unwrap();
1520        read_buf.data.clear();
1521        assert!(ld.main_read(&mut read_buf).is_ok());
1522        assert_eq!(read_buf.data, b"pong\n");
1523        read_buf.data.clear();
1524        assert_eq!(error!(EAGAIN), ld.replica_read(&mut read_buf));
1525        assert!(read_buf.data.is_empty());
1526
1527        // A TCOFLUSH from the main side should do nothing (instantaneous transmission)
1528        let mut ld = make_ld();
1529        ld.flush(TerminalSide::Main, uapi::TCOFLUSH).unwrap();
1530        read_buf.data.clear();
1531        assert!(ld.main_read(&mut read_buf).is_ok());
1532        assert_eq!(read_buf.data, b"pong\n");
1533        read_buf.data.clear();
1534        assert!(ld.replica_read(&mut read_buf).is_ok());
1535        assert_eq!(read_buf.data, b"ping\n");
1536
1537        // A TCOFLUSH from the replica side should do nothing
1538        let mut ld = make_ld();
1539        ld.flush(TerminalSide::Replica, uapi::TCOFLUSH).unwrap();
1540        read_buf.data.clear();
1541        assert!(ld.main_read(&mut read_buf).is_ok());
1542        assert_eq!(read_buf.data, b"pong\n");
1543        read_buf.data.clear();
1544        assert!(ld.replica_read(&mut read_buf).is_ok());
1545        assert_eq!(read_buf.data, b"ping\n");
1546
1547        // A TCIOFLUSH from main should flush only main's input (output_queue)
1548        let mut ld = make_ld();
1549        ld.flush(TerminalSide::Main, uapi::TCIOFLUSH).unwrap();
1550        read_buf.data.clear();
1551        assert_eq!(error!(EAGAIN), ld.main_read(&mut read_buf));
1552        read_buf.data.clear();
1553        assert!(ld.replica_read(&mut read_buf).is_ok());
1554        assert_eq!(read_buf.data, b"ping\n");
1555
1556        // A TCIOFLUSH from replica should flush only replica's input (input_queue)
1557        let mut ld = make_ld();
1558        ld.flush(TerminalSide::Replica, uapi::TCIOFLUSH).unwrap();
1559        read_buf.data.clear();
1560        assert!(ld.main_read(&mut read_buf).is_ok());
1561        assert_eq!(read_buf.data, b"pong\n");
1562        read_buf.data.clear();
1563        assert_eq!(error!(EAGAIN), ld.replica_read(&mut read_buf));
1564
1565        // A TCIFLUSH from replica while stopped via IXON should also clear pending_echoes.
1566        let mut ld = LineDiscipline::default();
1567        ld.main_open();
1568        ld.replica_open();
1569        let mut stop_and_text = TestBuffer { data: b"\x13stale\n".to_vec() };
1570        let _ = ld.main_write(&mut stop_and_text).unwrap();
1571        ld.flush(TerminalSide::Replica, uapi::TCIFLUSH).unwrap();
1572        let mut start_in = TestBuffer { data: b"\x11".to_vec() };
1573        let _ = ld.main_write(&mut start_in).unwrap();
1574        read_buf.data.clear();
1575        assert_eq!(error!(EAGAIN), ld.main_read(&mut read_buf));
1576    }
1577
1578    #[::fuchsia::test]
1579    fn test_canonical_max_line_length_with_erase_and_eof() {
1580        let mut ld = LineDiscipline::default();
1581        ld.main_open();
1582        ld.replica_open();
1583
1584        // Write 4096 'A's (only 4095 fit), then backspace (erases 4095th 'A'), then 'B', then '\n'.
1585        let mut payload = vec![b'A'; 4096];
1586        payload.extend_from_slice(b"\x7fB\n");
1587        let mut input = TestBuffer { data: payload };
1588        let (written, signals) = ld.main_write(&mut input).unwrap();
1589        assert_eq!(written, 4099);
1590        assert!(signals.signals().is_empty());
1591
1592        let mut replica_out = TestBuffer { data: vec![] };
1593        let (n, signals) = ld.replica_read(&mut replica_out).unwrap();
1594        assert_eq!(n, 4096);
1595        assert!(signals.signals().is_empty());
1596        assert_eq!(&replica_out.data[..4094], &[b'A'; 4094][..]);
1597        assert_eq!(&replica_out.data[4094..], b"B\n");
1598
1599        let mut main_out = TestBuffer { data: vec![] };
1600        let _ = ld.main_read(&mut main_out).unwrap();
1601        assert_eq!(&main_out.data[..4095], &[b'A'; 4095][..]);
1602        assert_eq!(&main_out.data[4095..], b"\x08 \x08B\r\n");
1603
1604        // Also test 4096 'C's terminated by VEOF (^D): replica should read 4095 'C's.
1605        let mut payload_eof = vec![b'C'; 4096];
1606        payload_eof.push(4);
1607        let mut input_eof = TestBuffer { data: payload_eof };
1608        let (written, _) = ld.main_write(&mut input_eof).unwrap();
1609        assert_eq!(written, 4097);
1610
1611        replica_out.data.clear();
1612        let (n, signals) = ld.replica_read(&mut replica_out).unwrap();
1613        assert_eq!(n, 4095);
1614        assert!(signals.signals().is_empty());
1615        assert_eq!(replica_out.data, vec![b'C'; 4095]);
1616        replica_out.data.clear();
1617        assert_eq!(ld.replica_read(&mut replica_out), error!(EAGAIN));
1618    }
1619
1620    #[::fuchsia::test]
1621    fn test_pty_cflag_enforced() {
1622        let mut ld = LineDiscipline::default();
1623        let mut termios = get_default_termios();
1624        termios.c_cflag = uapi::CS5 | uapi::PARENB;
1625        let _ = ld.set_termios(termios);
1626        assert_eq!(ld.termios().c_cflag & CSIZE, CS8);
1627        assert_eq!(ld.termios().c_cflag & CREAD, CREAD);
1628        assert_eq!(ld.termios().c_cflag & PARENB, 0);
1629    }
1630
1631    #[::fuchsia::test]
1632    fn test_close_events_and_eio() {
1633        let mut ld = LineDiscipline::default();
1634        ld.main_open();
1635        ld.replica_open();
1636
1637        ld.replica_close();
1638        assert!(ld.is_replica_closed());
1639        assert_eq!(ld.main_query_events(), FdEvents::POLLOUT | FdEvents::POLLHUP);
1640        let mut read_buf = TestBuffer { data: vec![] };
1641        assert_eq!(ld.main_read(&mut read_buf), error!(EIO));
1642
1643        ld.replica_open();
1644        let mut main_in = TestBuffer { data: b"unread\n".to_vec() };
1645        let _ = ld.main_write(&mut main_in).unwrap();
1646        assert_eq!(ld.get_available_read_size(TerminalSide::Replica), 7);
1647
1648        ld.main_close();
1649        assert!(ld.is_main_closed());
1650        assert_eq!(ld.get_available_read_size(TerminalSide::Replica), 0);
1651        assert_eq!(ld.get_available_read_size(TerminalSide::Main), 0);
1652        assert_eq!(
1653            ld.replica_query_events(),
1654            FdEvents::POLLIN | FdEvents::POLLOUT | FdEvents::POLLERR | FdEvents::POLLHUP
1655        );
1656        let (n, signals) = ld.replica_read(&mut read_buf).unwrap();
1657        assert_eq!(n, 0);
1658        assert!(signals.signals().is_empty());
1659        let mut write_buf = TestBuffer { data: b"x".to_vec() };
1660        assert_eq!(ld.replica_write(&mut write_buf), error!(EIO));
1661    }
1662
1663    #[::fuchsia::test]
1664    fn test_parmrk_capacity_limits() {
1665        let mut ld = LineDiscipline::default();
1666        ld.main_open();
1667        ld.replica_open();
1668
1669        let mut termios = get_default_termios();
1670        termios.c_iflag |= PARMRK;
1671        termios.c_lflag &= !ECHO;
1672        termios.c_cc[VEOL as usize] = b';';
1673        let _ = ld.set_termios(termios);
1674
1675        // Canonical mode: 4094 'A's + 0xff (would require 2 bytes = 4096, leaving no room for EOL)
1676        // must drop the 0xff so the subsequent '\n' still terminates a 4095-byte line.
1677        let mut payload = vec![b'A'; 4094];
1678        payload.extend_from_slice(&[0xff, b'\n']);
1679        let mut input = TestBuffer { data: payload };
1680        let (written, _) = ld.main_write(&mut input).unwrap();
1681        assert_eq!(written, 4096);
1682
1683        let mut replica_out = TestBuffer { data: vec![] };
1684        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1685        assert_eq!(n, 4095);
1686        assert_eq!(&replica_out.data[..4094], &[b'A'; 4094][..]);
1687        assert_eq!(replica_out.data[4094], b'\n');
1688
1689        // Canonical mode with VEOL = 0xff: 4095 'A's + 0xff (2 bytes > 4096) must not exceed
1690        // CANON_MAX_BYTES.
1691        termios.c_cc[VEOL as usize] = 0xff;
1692        let _ = ld.set_termios(termios);
1693        let mut payload_veol = vec![b'A'; 4095];
1694        payload_veol.extend_from_slice(&[0xff, b'\n']);
1695        let mut input_veol = TestBuffer { data: payload_veol };
1696        let _ = ld.main_write(&mut input_veol).unwrap();
1697        replica_out.data.clear();
1698        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1699        assert_eq!(n, 4096);
1700        assert_eq!(&replica_out.data[..4095], &[b'A'; 4095][..]);
1701        assert_eq!(replica_out.data[4095], b'\n');
1702
1703        // Non-canonical mode: 4094 'A's + 0xff (2 bytes -> 4096 > NON_CANON_MAX_BYTES = 4095)
1704        // must stop before the 0xff so the first chunk is 4094 bytes and the second is [0xff, 0xff].
1705        termios.c_lflag &= !ICANON;
1706        let _ = ld.set_termios(termios);
1707        let mut payload_noncanon = vec![b'A'; 4094];
1708        payload_noncanon.push(0xff);
1709        let mut input_noncanon = TestBuffer { data: payload_noncanon };
1710        let (written, _) = ld.main_write(&mut input_noncanon).unwrap();
1711        assert_eq!(written, 4095);
1712        assert_eq!(ld.get_available_read_size(TerminalSide::Replica), 4094);
1713        replica_out.data.clear();
1714        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1715        assert_eq!(n, 4094);
1716        replica_out.data.clear();
1717        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1718        assert_eq!(n, 2);
1719        assert_eq!(replica_out.data, vec![0xff, 0xff]);
1720    }
1721
1722    #[::fuchsia::test]
1723    fn test_stopped_output_wait_buffer_not_drained_until_start() {
1724        let mut ld = LineDiscipline::default();
1725        ld.main_open();
1726        ld.replica_open();
1727
1728        // Write 5000 'A's from replica: 4096 are transformed into output_queue.read_queue and 904
1729        // remain in output_queue.wait_buffers.
1730        let mut replica_in = TestBuffer { data: vec![b'A'; 5000] };
1731        let written = ld.replica_write(&mut replica_in).unwrap();
1732        assert_eq!(written, 5000);
1733        assert_eq!(ld.get_available_read_size(TerminalSide::Main), 4096);
1734
1735        // Send VSTOP (^S) from main to stop output.
1736        let mut stop_in = TestBuffer { data: vec![0x13] };
1737        let (written, signals) = ld.main_write(&mut stop_in).unwrap();
1738        assert_eq!(written, 1);
1739        assert!(signals.signals().is_empty());
1740
1741        // Reading from main consumes the 4096 already-transformed bytes, but must NOT drain the
1742        // remaining 904 bytes from output_queue.wait_buffers while stopped.
1743        let mut main_out = TestBuffer { data: vec![] };
1744        let n = ld.main_read(&mut main_out).unwrap();
1745        assert_eq!(n, 4096);
1746        assert_eq!(main_out.data.len(), 4096);
1747        assert_eq!(ld.get_available_read_size(TerminalSide::Main), 0);
1748
1749        main_out.data.clear();
1750        assert_eq!(ld.main_read(&mut main_out), error!(EAGAIN));
1751
1752        // Send VSTART (^Q) from main to resume output, which drains output_queue.wait_buffers.
1753        let mut start_in = TestBuffer { data: vec![0x11] };
1754        let (written, signals) = ld.main_write(&mut start_in).unwrap();
1755        assert_eq!(written, 1);
1756        assert!(signals.signals().is_empty());
1757        assert_eq!(ld.get_available_read_size(TerminalSide::Main), 904);
1758
1759        let n = ld.main_read(&mut main_out).unwrap();
1760        assert_eq!(n, 904);
1761        assert_eq!(main_out.data, vec![b'A'; 904]);
1762    }
1763
1764    #[::fuchsia::test]
1765    fn test_veof_and_unread_queue_buffer_capacity() {
1766        let mut ld = LineDiscipline::default();
1767        ld.main_open();
1768        ld.replica_open();
1769
1770        // Each empty VEOF (^D) occupies 1 byte in `buffer_len()` (matching `__DISABLED_CHAR` in
1771        // Linux `n_tty`), so at most `NON_CANON_MAX_BYTES` (4095) empty VEOF packets are drained
1772        // into `read_queue` while the 4096th remains in `wait_buffers`.
1773        let mut eofs = TestBuffer { data: vec![0x04; 4096] };
1774        let (written, _) = ld.main_write(&mut eofs).unwrap();
1775        assert_eq!(written, 4096);
1776        assert_eq!(ld.input_queue().read_queue.len(), NON_CANON_MAX_BYTES);
1777        assert_eq!(ld.input_queue().total_wait_buffer_length, 1);
1778
1779        // Reading 1 VEOF frees 1 slot and drains the 4096th VEOF from `wait_buffers`.
1780        let mut replica_out = TestBuffer { data: vec![] };
1781        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1782        assert_eq!(n, 0);
1783        assert_eq!(ld.input_queue().read_queue.len(), NON_CANON_MAX_BYTES);
1784        assert_eq!(ld.input_queue().total_wait_buffer_length, 0);
1785    }
1786
1787    #[::fuchsia::test]
1788    fn test_iutf8_multibyte_at_canon_boundary() {
1789        let mut ld = LineDiscipline::default();
1790        ld.main_open();
1791        ld.replica_open();
1792
1793        let mut termios = get_default_termios();
1794        termios.c_iflag |= IUTF8;
1795        termios.c_lflag &= !ECHO;
1796        let _ = ld.set_termios(termios);
1797
1798        // 4094 'A's + "\xc3\xa9\n": Linux `n_tty` accepts the first byte 0xc3 (reaching 4095
1799        // non-EOL bytes), drops 0xa9, and accepts '\n' for a 4096-byte line.
1800        let mut payload = vec![b'A'; 4094];
1801        payload.extend_from_slice(b"\xc3\xa9\n");
1802        let mut input = TestBuffer { data: payload };
1803        let (written, _) = ld.main_write(&mut input).unwrap();
1804        assert_eq!(written, 4097);
1805
1806        let mut replica_out = TestBuffer { data: vec![] };
1807        let (n, _) = ld.replica_read(&mut replica_out).unwrap();
1808        assert_eq!(n, 4096);
1809        assert_eq!(&replica_out.data[..4094], &[b'A'; 4094][..]);
1810        assert_eq!(&replica_out.data[4094..], b"\xc3\n");
1811    }
1812
1813    #[::fuchsia::test]
1814    fn test_signal_flush_preserves_subsequent_wait_buffers() {
1815        let mut ld = LineDiscipline::default();
1816        ld.main_open();
1817        ld.replica_open();
1818
1819        // Fill `read_queue` to 4095 bytes (so `buffer_len() == NON_CANON_MAX_BYTES` and subsequent
1820        // writes queue in `wait_buffers`).
1821        let mut fill = vec![b'A'; 4094];
1822        fill.push(b'\n');
1823        let mut fill_in = TestBuffer { data: fill };
1824        let (written, _) = ld.main_write(&mut fill_in).unwrap();
1825        assert_eq!(written, 4095);
1826
1827        // Queue two separate `main_write` buffers while full: one with VINTR (^C) and one with
1828        // "after\n".
1829        let mut sig_in = TestBuffer { data: b"\x03".to_vec() };
1830        let (written, signals) = ld.main_write(&mut sig_in).unwrap();
1831        assert_eq!(written, 1);
1832        assert!(signals.signals().is_empty());
1833
1834        let mut after_in = TestBuffer { data: b"after\n".to_vec() };
1835        let (written, signals) = ld.main_write(&mut after_in).unwrap();
1836        assert_eq!(written, 6);
1837        assert!(signals.signals().is_empty());
1838
1839        // Reading the initial 4095-byte line drains `wait_buffers`: `^C` raises SIGINT and flushes
1840        // any prior input, then `"after\n"` is processed into `read_queue`.
1841        let mut replica_out = TestBuffer { data: vec![] };
1842        let (n, signals) = ld.replica_read(&mut replica_out).unwrap();
1843        assert_eq!(n, 4095);
1844        assert_eq!(signals.signals(), &[SIGINT]);
1845
1846        replica_out.data.clear();
1847        let (n, signals) = ld.replica_read(&mut replica_out).unwrap();
1848        assert_eq!(n, 6);
1849        assert!(signals.signals().is_empty());
1850        assert_eq!(replica_out.data, b"after\n");
1851    }
1852}
1853
1854pub mod testing;