Skip to main content

secure_exec_kernel/
pty.rs

1use crate::fd_table::{
2    FdResult, FileDescription, ProcessFdTable, SharedFileDescription, FILETYPE_CHARACTER_DEVICE,
3    O_RDWR,
4};
5use crate::poll::{PollEvents, PollNotifier, POLLHUP, POLLIN, POLLOUT};
6use std::collections::{BTreeMap, VecDeque};
7use std::error::Error;
8use std::fmt;
9use std::sync::{Arc, Condvar, Mutex, MutexGuard};
10use std::time::{Duration, Instant};
11
12pub const MAX_PTY_BUFFER_BYTES: usize = 65_536;
13pub const MAX_CANON: usize = 4_096;
14pub const SIGINT: i32 = 2;
15pub const SIGQUIT: i32 = 3;
16pub const SIGTSTP: i32 = 20;
17const DEFAULT_PTY_COLUMNS: u16 = 80;
18const DEFAULT_PTY_ROWS: u16 = 24;
19
20pub type PtyResult<T> = Result<T, PtyError>;
21pub type SignalHandler = Arc<dyn Fn(u32, i32) + Send + Sync>;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct PtyError {
25    code: &'static str,
26    message: String,
27}
28
29impl PtyError {
30    pub fn code(&self) -> &'static str {
31        self.code
32    }
33
34    fn bad_file_descriptor(message: impl Into<String>) -> Self {
35        Self {
36            code: "EBADF",
37            message: message.into(),
38        }
39    }
40
41    fn io(message: impl Into<String>) -> Self {
42        Self {
43            code: "EIO",
44            message: message.into(),
45        }
46    }
47
48    fn would_block(message: impl Into<String>) -> Self {
49        Self {
50            code: "EAGAIN",
51            message: message.into(),
52        }
53    }
54}
55
56impl fmt::Display for PtyError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}: {}", self.code, self.message)
59    }
60}
61
62impl Error for PtyError {}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub struct LineDisciplineConfig {
66    pub canonical: Option<bool>,
67    pub echo: Option<bool>,
68    pub isig: Option<bool>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct Termios {
73    pub icrnl: bool,
74    pub opost: bool,
75    pub onlcr: bool,
76    pub icanon: bool,
77    pub echo: bool,
78    pub isig: bool,
79    pub cc: TermiosControlChars,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub struct PartialTermios {
84    pub icrnl: Option<bool>,
85    pub opost: Option<bool>,
86    pub onlcr: Option<bool>,
87    pub icanon: Option<bool>,
88    pub echo: Option<bool>,
89    pub isig: Option<bool>,
90    pub cc: Option<PartialTermiosControlChars>,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct TermiosControlChars {
95    pub vintr: u8,
96    pub vquit: u8,
97    pub vsusp: u8,
98    pub veof: u8,
99    pub verase: u8,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub struct PartialTermiosControlChars {
104    pub vintr: Option<u8>,
105    pub vquit: Option<u8>,
106    pub vsusp: Option<u8>,
107    pub veof: Option<u8>,
108    pub verase: Option<u8>,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct PtyWindowSize {
113    pub cols: u16,
114    pub rows: u16,
115}
116
117impl Default for PtyWindowSize {
118    fn default() -> Self {
119        Self {
120            cols: DEFAULT_PTY_COLUMNS,
121            rows: DEFAULT_PTY_ROWS,
122        }
123    }
124}
125
126impl Default for Termios {
127    fn default() -> Self {
128        Self {
129            icrnl: true,
130            opost: true,
131            onlcr: true,
132            icanon: true,
133            echo: true,
134            isig: true,
135            cc: TermiosControlChars {
136                vintr: 0x03,
137                vquit: 0x1c,
138                vsusp: 0x1a,
139                veof: 0x04,
140                verase: 0x7f,
141            },
142        }
143    }
144}
145
146impl Termios {
147    fn merge(&mut self, update: PartialTermios) {
148        if let Some(icrnl) = update.icrnl {
149            self.icrnl = icrnl;
150        }
151        if let Some(opost) = update.opost {
152            self.opost = opost;
153        }
154        if let Some(onlcr) = update.onlcr {
155            self.onlcr = onlcr;
156        }
157        if let Some(icanon) = update.icanon {
158            self.icanon = icanon;
159        }
160        if let Some(echo) = update.echo {
161            self.echo = echo;
162        }
163        if let Some(isig) = update.isig {
164            self.isig = isig;
165        }
166        if let Some(cc) = update.cc {
167            self.cc.merge(cc);
168        }
169    }
170}
171
172impl TermiosControlChars {
173    fn merge(&mut self, update: PartialTermiosControlChars) {
174        if let Some(vintr) = update.vintr {
175            self.vintr = vintr;
176        }
177        if let Some(vquit) = update.vquit {
178            self.vquit = vquit;
179        }
180        if let Some(vsusp) = update.vsusp {
181            self.vsusp = vsusp;
182        }
183        if let Some(veof) = update.veof {
184            self.veof = veof;
185        }
186        if let Some(verase) = update.verase {
187            self.verase = verase;
188        }
189    }
190}
191
192#[derive(Debug, Clone)]
193pub struct PtyEnd {
194    pub description: SharedFileDescription,
195    pub filetype: u8,
196}
197
198#[derive(Debug, Clone)]
199pub struct PtyPair {
200    pub master: PtyEnd,
201    pub slave: PtyEnd,
202    pub path: String,
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206struct PtyRef {
207    pty_id: u64,
208    end: PtyEndKind,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212enum PtyEndKind {
213    Master,
214    Slave,
215}
216
217#[derive(Debug, Default)]
218struct PendingRead {
219    length: usize,
220    result: Option<Option<Vec<u8>>>,
221}
222
223#[derive(Debug, Clone, Default)]
224struct PtyState {
225    path: String,
226    input_buffer: VecDeque<Vec<u8>>,
227    output_buffer: VecDeque<Vec<u8>>,
228    closed_master: bool,
229    closed_slave: bool,
230    waiting_input_reads: VecDeque<u64>,
231    waiting_output_reads: VecDeque<u64>,
232    termios: Termios,
233    line_buffer: Vec<u8>,
234    foreground_pgid: u32,
235    window_size: PtyWindowSize,
236}
237
238#[derive(Debug)]
239struct PtyManagerState {
240    ptys: BTreeMap<u64, PtyState>,
241    desc_to_pty: BTreeMap<u64, PtyRef>,
242    waiters: BTreeMap<u64, PendingRead>,
243    next_pty_id: u64,
244    next_desc_id: u64,
245    next_waiter_id: u64,
246}
247
248impl Default for PtyManagerState {
249    fn default() -> Self {
250        Self {
251            ptys: BTreeMap::new(),
252            desc_to_pty: BTreeMap::new(),
253            waiters: BTreeMap::new(),
254            next_pty_id: 0,
255            next_desc_id: 200_000,
256            next_waiter_id: 1,
257        }
258    }
259}
260
261#[derive(Debug)]
262struct PtyManagerInner {
263    state: Mutex<PtyManagerState>,
264    waiters: Condvar,
265}
266
267#[derive(Clone)]
268pub struct PtyManager {
269    inner: Arc<PtyManagerInner>,
270    on_signal: Option<SignalHandler>,
271    notifier: Option<PollNotifier>,
272}
273
274impl Default for PtyManager {
275    fn default() -> Self {
276        Self {
277            inner: Arc::new(PtyManagerInner {
278                state: Mutex::new(PtyManagerState::default()),
279                waiters: Condvar::new(),
280            }),
281            on_signal: None,
282            notifier: None,
283        }
284    }
285}
286
287impl PtyManager {
288    pub fn new() -> Self {
289        Self::default()
290    }
291
292    pub fn with_signal_handler(on_signal: SignalHandler) -> Self {
293        let mut manager = Self::new();
294        manager.on_signal = Some(on_signal);
295        manager
296    }
297
298    pub(crate) fn with_signal_handler_and_notifier(
299        on_signal: SignalHandler,
300        notifier: PollNotifier,
301    ) -> Self {
302        let mut manager = Self::with_notifier(notifier);
303        manager.on_signal = Some(on_signal);
304        manager
305    }
306
307    pub(crate) fn with_notifier(notifier: PollNotifier) -> Self {
308        Self {
309            notifier: Some(notifier),
310            ..Self::default()
311        }
312    }
313
314    pub fn create_pty(&self) -> PtyPair {
315        let mut state = lock_or_recover(&self.inner.state);
316        let pty_id = state.next_pty_id;
317        state.next_pty_id += 1;
318
319        let master_id = state.next_desc_id;
320        state.next_desc_id += 1;
321        let slave_id = state.next_desc_id;
322        state.next_desc_id += 1;
323
324        let path = format!("/dev/pts/{pty_id}");
325        state.ptys.insert(
326            pty_id,
327            PtyState {
328                path: path.clone(),
329                termios: Termios::default(),
330                window_size: PtyWindowSize::default(),
331                ..PtyState::default()
332            },
333        );
334        state.desc_to_pty.insert(
335            master_id,
336            PtyRef {
337                pty_id,
338                end: PtyEndKind::Master,
339            },
340        );
341        state.desc_to_pty.insert(
342            slave_id,
343            PtyRef {
344                pty_id,
345                end: PtyEndKind::Slave,
346            },
347        );
348        drop(state);
349
350        PtyPair {
351            master: PtyEnd {
352                description: Arc::new(FileDescription::with_ref_count(
353                    master_id,
354                    format!("pty:{pty_id}:master"),
355                    O_RDWR,
356                    0,
357                )),
358                filetype: FILETYPE_CHARACTER_DEVICE,
359            },
360            slave: PtyEnd {
361                description: Arc::new(FileDescription::with_ref_count(
362                    slave_id,
363                    path.clone(),
364                    O_RDWR,
365                    0,
366                )),
367                filetype: FILETYPE_CHARACTER_DEVICE,
368            },
369            path,
370        }
371    }
372
373    pub fn create_pty_fds(&self, fd_table: &mut ProcessFdTable) -> FdResult<(u32, u32, String)> {
374        let pty = self.create_pty();
375        let master_fd = fd_table.open_with(
376            Arc::clone(&pty.master.description),
377            FILETYPE_CHARACTER_DEVICE,
378            None,
379        )?;
380        match fd_table.open_with(
381            Arc::clone(&pty.slave.description),
382            FILETYPE_CHARACTER_DEVICE,
383            None,
384        ) {
385            Ok(slave_fd) => Ok((master_fd, slave_fd, pty.path)),
386            Err(error) => {
387                fd_table.close(master_fd);
388                self.close(pty.master.description.id());
389                self.close(pty.slave.description.id());
390                Err(error)
391            }
392        }
393    }
394
395    pub fn poll(&self, description_id: u64, requested: PollEvents) -> PtyResult<PollEvents> {
396        let state = lock_or_recover(&self.inner.state);
397        let pty_ref = state
398            .desc_to_pty
399            .get(&description_id)
400            .copied()
401            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
402        let pty = state
403            .ptys
404            .get(&pty_ref.pty_id)
405            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
406
407        let mut events = PollEvents::empty();
408        match pty_ref.end {
409            PtyEndKind::Master => {
410                if requested.intersects(POLLIN) && !pty.output_buffer.is_empty() {
411                    events |= POLLIN;
412                }
413                if pty.closed_slave {
414                    events |= POLLHUP;
415                } else if requested.intersects(POLLOUT)
416                    && (available_capacity(&pty.input_buffer) > 0
417                        || !pty.waiting_input_reads.is_empty())
418                {
419                    events |= POLLOUT;
420                }
421            }
422            PtyEndKind::Slave => {
423                if requested.intersects(POLLIN) && !pty.input_buffer.is_empty() {
424                    events |= POLLIN;
425                }
426                if pty.closed_master {
427                    events |= POLLHUP;
428                } else if requested.intersects(POLLOUT)
429                    && (available_capacity(&pty.output_buffer) > 0
430                        || !pty.waiting_output_reads.is_empty())
431                {
432                    events |= POLLOUT;
433                }
434            }
435        }
436
437        Ok(events)
438    }
439
440    pub fn write(&self, description_id: u64, data: impl AsRef<[u8]>) -> PtyResult<usize> {
441        let payload = data.as_ref();
442        let mut signals = Vec::new();
443
444        {
445            let mut state = lock_or_recover(&self.inner.state);
446            let pty_ref = state
447                .desc_to_pty
448                .get(&description_id)
449                .copied()
450                .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
451            let PtyManagerState { ptys, waiters, .. } = &mut *state;
452            let pty = ptys
453                .get_mut(&pty_ref.pty_id)
454                .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
455
456            match pty_ref.end {
457                PtyEndKind::Master => {
458                    if pty.closed_master {
459                        return Err(PtyError::io("master closed"));
460                    }
461                    if pty.closed_slave {
462                        return Err(PtyError::io("slave closed"));
463                    }
464                    process_input(pty, waiters, payload, &mut signals)?;
465                }
466                PtyEndKind::Slave => {
467                    if pty.closed_slave {
468                        return Err(PtyError::io("slave closed"));
469                    }
470                    if pty.closed_master {
471                        return Err(PtyError::io("master closed"));
472                    }
473
474                    let processed = process_output(&pty.termios, payload);
475                    deliver_output(pty, waiters, &processed, false)?;
476                }
477            }
478        }
479
480        self.notify_waiters_and_pollers();
481        if let Some(on_signal) = &self.on_signal {
482            for (pgid, signal) in signals {
483                if pgid > 0 {
484                    on_signal(pgid, signal);
485                }
486            }
487        }
488
489        Ok(payload.len())
490    }
491
492    pub fn read(&self, description_id: u64, length: usize) -> PtyResult<Option<Vec<u8>>> {
493        self.read_with_timeout(description_id, length, None)
494    }
495
496    pub fn read_with_timeout(
497        &self,
498        description_id: u64,
499        length: usize,
500        timeout: Option<Duration>,
501    ) -> PtyResult<Option<Vec<u8>>> {
502        let mut state = lock_or_recover(&self.inner.state);
503        let pty_ref = state
504            .desc_to_pty
505            .get(&description_id)
506            .copied()
507            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
508        let mut waiter_id = None;
509        let deadline = timeout.map(|duration| Instant::now() + duration);
510
511        loop {
512            if let Some(id) = waiter_id {
513                if let Some(waiter) = state.waiters.get_mut(&id) {
514                    if let Some(result) = waiter.result.take() {
515                        state.waiters.remove(&id);
516                        return Ok(result);
517                    }
518                }
519            }
520
521            {
522                let pty = state
523                    .ptys
524                    .get_mut(&pty_ref.pty_id)
525                    .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
526
527                match pty_ref.end {
528                    PtyEndKind::Master => {
529                        if pty.closed_master {
530                            if let Some(id) = waiter_id {
531                                state.waiters.remove(&id);
532                            }
533                            return Err(PtyError::io("master closed"));
534                        }
535
536                        if !pty.output_buffer.is_empty() {
537                            let result = drain_buffer(&mut pty.output_buffer, length);
538                            // This reader consumed buffered data directly, so its queued waiter
539                            // entry must be removed or a later delivery will assign data to an
540                            // orphan.
541                            if let Some(id) = waiter_id.take() {
542                                pty.waiting_input_reads.retain(|queued| *queued != id);
543                                pty.waiting_output_reads.retain(|queued| *queued != id);
544                                state.waiters.remove(&id);
545                            }
546                            self.notify_waiters_and_pollers();
547                            return Ok(Some(result));
548                        }
549
550                        if pty.closed_slave {
551                            if let Some(id) = waiter_id {
552                                state.waiters.remove(&id);
553                            }
554                            return Ok(None);
555                        }
556                    }
557                    PtyEndKind::Slave => {
558                        if pty.closed_slave {
559                            if let Some(id) = waiter_id {
560                                state.waiters.remove(&id);
561                            }
562                            return Err(PtyError::io("slave closed"));
563                        }
564
565                        if !pty.input_buffer.is_empty() {
566                            let result = drain_buffer(&mut pty.input_buffer, length);
567                            // This reader consumed buffered data directly, so its queued waiter
568                            // entry must be removed or a later delivery will assign data to an
569                            // orphan.
570                            if let Some(id) = waiter_id.take() {
571                                pty.waiting_input_reads.retain(|queued| *queued != id);
572                                pty.waiting_output_reads.retain(|queued| *queued != id);
573                                state.waiters.remove(&id);
574                            }
575                            self.notify_waiters_and_pollers();
576                            return Ok(Some(result));
577                        }
578
579                        if pty.closed_master {
580                            if let Some(id) = waiter_id {
581                                state.waiters.remove(&id);
582                            }
583                            return Ok(None);
584                        }
585                    }
586                }
587            }
588
589            let id = if let Some(id) = waiter_id {
590                id
591            } else {
592                let next = state.next_waiter_id;
593                state.next_waiter_id += 1;
594                state.waiters.insert(
595                    next,
596                    PendingRead {
597                        length,
598                        result: None,
599                    },
600                );
601                let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) else {
602                    state.waiters.remove(&next);
603                    return Err(PtyError::bad_file_descriptor("PTY not found"));
604                };
605                match pty_ref.end {
606                    PtyEndKind::Master => pty.waiting_output_reads.push_back(next),
607                    PtyEndKind::Slave => pty.waiting_input_reads.push_back(next),
608                }
609                self.notify_waiters_and_pollers();
610                waiter_id = Some(next);
611                next
612            };
613
614            let Some(deadline) = deadline else {
615                state = wait_or_recover(&self.inner.waiters, state);
616                if !state.waiters.contains_key(&id) {
617                    waiter_id = None;
618                }
619                continue;
620            };
621
622            let now = Instant::now();
623            if now >= deadline {
624                if let Some(id) = waiter_id.take() {
625                    state.waiters.remove(&id);
626                    if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
627                        pty.waiting_input_reads.retain(|queued| *queued != id);
628                        pty.waiting_output_reads.retain(|queued| *queued != id);
629                    }
630                    self.notify_waiters_and_pollers();
631                }
632                return Err(PtyError::would_block("PTY read timed out"));
633            }
634
635            let remaining = deadline.saturating_duration_since(now);
636            let (next_state, wait_result) =
637                wait_timeout_or_recover(&self.inner.waiters, state, remaining);
638            state = next_state;
639            if !state.waiters.contains_key(&id) {
640                waiter_id = None;
641            }
642            if wait_result.timed_out() {
643                if let Some(id) = waiter_id.take() {
644                    state.waiters.remove(&id);
645                    if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
646                        pty.waiting_input_reads.retain(|queued| *queued != id);
647                        pty.waiting_output_reads.retain(|queued| *queued != id);
648                    }
649                    self.notify_waiters_and_pollers();
650                }
651                return Err(PtyError::would_block("PTY read timed out"));
652            }
653        }
654    }
655
656    pub fn close(&self, description_id: u64) {
657        let mut state = lock_or_recover(&self.inner.state);
658        let Some(pty_ref) = state.desc_to_pty.remove(&description_id) else {
659            return;
660        };
661
662        let (waiter_ids, remove_pty) = if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
663            match pty_ref.end {
664                PtyEndKind::Master => {
665                    pty.closed_master = true;
666                    let mut waiters = pty.waiting_input_reads.drain(..).collect::<Vec<_>>();
667                    waiters.extend(pty.waiting_output_reads.drain(..));
668                    (waiters, pty.closed_master && pty.closed_slave)
669                }
670                PtyEndKind::Slave => {
671                    pty.closed_slave = true;
672                    let mut waiters = pty.waiting_output_reads.drain(..).collect::<Vec<_>>();
673                    waiters.extend(pty.waiting_input_reads.drain(..));
674                    (waiters, pty.closed_master && pty.closed_slave)
675                }
676            }
677        } else {
678            (Vec::new(), false)
679        };
680
681        for waiter_id in waiter_ids {
682            if let Some(waiter) = state.waiters.get_mut(&waiter_id) {
683                waiter.result = Some(None);
684            }
685        }
686
687        if remove_pty {
688            state.ptys.remove(&pty_ref.pty_id);
689        }
690        self.notify_waiters_and_pollers();
691    }
692
693    pub fn is_pty(&self, description_id: u64) -> bool {
694        lock_or_recover(&self.inner.state)
695            .desc_to_pty
696            .contains_key(&description_id)
697    }
698
699    pub fn is_slave(&self, description_id: u64) -> bool {
700        lock_or_recover(&self.inner.state)
701            .desc_to_pty
702            .get(&description_id)
703            .map(|pty_ref| pty_ref.end == PtyEndKind::Slave)
704            .unwrap_or(false)
705    }
706
707    pub fn set_discipline(
708        &self,
709        description_id: u64,
710        config: LineDisciplineConfig,
711    ) -> PtyResult<()> {
712        let mut state = lock_or_recover(&self.inner.state);
713        let pty_ref = state
714            .desc_to_pty
715            .get(&description_id)
716            .copied()
717            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
718        let pty = state
719            .ptys
720            .get_mut(&pty_ref.pty_id)
721            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
722        if let Some(canonical) = config.canonical {
723            pty.termios.icanon = canonical;
724        }
725        if let Some(echo) = config.echo {
726            pty.termios.echo = echo;
727        }
728        if let Some(isig) = config.isig {
729            pty.termios.isig = isig;
730        }
731        Ok(())
732    }
733
734    pub fn get_termios(&self, description_id: u64) -> PtyResult<Termios> {
735        let state = lock_or_recover(&self.inner.state);
736        let pty_ref = state
737            .desc_to_pty
738            .get(&description_id)
739            .copied()
740            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
741        state
742            .ptys
743            .get(&pty_ref.pty_id)
744            .cloned()
745            .map(|pty| pty.termios)
746            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))
747    }
748
749    pub fn set_termios(&self, description_id: u64, termios: PartialTermios) -> PtyResult<()> {
750        let mut state = lock_or_recover(&self.inner.state);
751        let pty_ref = state
752            .desc_to_pty
753            .get(&description_id)
754            .copied()
755            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
756        let pty = state
757            .ptys
758            .get_mut(&pty_ref.pty_id)
759            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
760        pty.termios.merge(termios);
761        Ok(())
762    }
763
764    pub fn set_foreground_pgid(&self, description_id: u64, pgid: u32) -> PtyResult<()> {
765        let mut state = lock_or_recover(&self.inner.state);
766        let pty_ref = state
767            .desc_to_pty
768            .get(&description_id)
769            .copied()
770            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
771        let pty = state
772            .ptys
773            .get_mut(&pty_ref.pty_id)
774            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
775        pty.foreground_pgid = pgid;
776        Ok(())
777    }
778
779    pub fn get_foreground_pgid(&self, description_id: u64) -> PtyResult<u32> {
780        let state = lock_or_recover(&self.inner.state);
781        let pty_ref = state
782            .desc_to_pty
783            .get(&description_id)
784            .copied()
785            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
786        state
787            .ptys
788            .get(&pty_ref.pty_id)
789            .map(|pty| pty.foreground_pgid)
790            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))
791    }
792
793    pub fn resize(&self, description_id: u64, cols: u16, rows: u16) -> PtyResult<Option<u32>> {
794        let mut state = lock_or_recover(&self.inner.state);
795        let pty_ref = state
796            .desc_to_pty
797            .get(&description_id)
798            .copied()
799            .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
800        let pty = state
801            .ptys
802            .get_mut(&pty_ref.pty_id)
803            .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
804        let next_size = PtyWindowSize { cols, rows };
805        if pty.window_size == next_size {
806            return Ok(None);
807        }
808        pty.window_size = next_size;
809        Ok((pty.foreground_pgid > 0).then_some(pty.foreground_pgid))
810    }
811
812    pub fn pty_count(&self) -> usize {
813        lock_or_recover(&self.inner.state).ptys.len()
814    }
815
816    pub fn buffered_input_bytes(&self) -> usize {
817        lock_or_recover(&self.inner.state)
818            .ptys
819            .values()
820            .map(|pty| buffer_size(&pty.input_buffer))
821            .sum()
822    }
823
824    pub fn buffered_output_bytes(&self) -> usize {
825        lock_or_recover(&self.inner.state)
826            .ptys
827            .values()
828            .map(|pty| buffer_size(&pty.output_buffer))
829            .sum()
830    }
831
832    pub fn pending_read_waiter_count(&self) -> usize {
833        lock_or_recover(&self.inner.state).waiters.len()
834    }
835
836    pub fn queued_read_waiter_count(&self) -> usize {
837        lock_or_recover(&self.inner.state)
838            .ptys
839            .values()
840            .map(|pty| pty.waiting_input_reads.len() + pty.waiting_output_reads.len())
841            .sum()
842    }
843
844    pub fn path_for(&self, description_id: u64) -> Option<String> {
845        let state = lock_or_recover(&self.inner.state);
846        let pty_ref = state.desc_to_pty.get(&description_id)?;
847        state.ptys.get(&pty_ref.pty_id).map(|pty| pty.path.clone())
848    }
849
850    fn notify_waiters_and_pollers(&self) {
851        self.inner.waiters.notify_all();
852        if let Some(notifier) = &self.notifier {
853            notifier.notify();
854        }
855    }
856}
857
858fn process_output(termios: &Termios, data: &[u8]) -> Vec<u8> {
859    if !termios.opost || !termios.onlcr || !data.contains(&b'\n') {
860        return data.to_vec();
861    }
862
863    let extra_crs = data
864        .iter()
865        .enumerate()
866        .filter(|(index, byte)| **byte == b'\n' && (*index == 0 || data[*index - 1] != b'\r'))
867        .count();
868    if extra_crs == 0 {
869        return data.to_vec();
870    }
871
872    let mut result = Vec::with_capacity(data.len() + extra_crs);
873    for (index, byte) in data.iter().enumerate() {
874        if *byte == b'\n' && (index == 0 || data[index - 1] != b'\r') {
875            result.push(b'\r');
876        }
877        result.push(*byte);
878    }
879    result
880}
881
882fn process_input(
883    pty: &mut PtyState,
884    waiters: &mut BTreeMap<u64, PendingRead>,
885    data: &[u8],
886    signals: &mut Vec<(u32, i32)>,
887) -> PtyResult<()> {
888    if !pty.termios.icanon && !pty.termios.echo && !pty.termios.isig {
889        let translated = translate_input(&pty.termios, data);
890        deliver_input(pty, waiters, &translated)?;
891        return Ok(());
892    }
893
894    for mut byte in data.iter().copied() {
895        if pty.termios.icrnl && byte == b'\r' {
896            byte = b'\n';
897        }
898
899        if pty.termios.isig {
900            if let Some(signal) = signal_for_byte(&pty.termios, byte) {
901                if pty.termios.icanon {
902                    pty.line_buffer.clear();
903                }
904                if pty.foreground_pgid > 0 {
905                    signals.push((pty.foreground_pgid, signal));
906                }
907                continue;
908            }
909        }
910
911        if pty.termios.icanon {
912            if byte == pty.termios.cc.veof {
913                if pty.line_buffer.is_empty() {
914                    deliver_input(pty, waiters, &[])?;
915                } else {
916                    let line = pty.line_buffer.clone();
917                    deliver_input(pty, waiters, &line)?;
918                    pty.line_buffer.clear();
919                }
920                continue;
921            }
922
923            if byte == pty.termios.cc.verase || byte == 0x08 {
924                if !pty.line_buffer.is_empty() {
925                    if pty.termios.echo {
926                        deliver_output(pty, waiters, &[0x08, 0x20, 0x08], true)?;
927                    }
928                    pty.line_buffer.pop();
929                }
930                continue;
931            }
932
933            if byte == b'\n' {
934                let mut line = pty.line_buffer.clone();
935                line.push(b'\n');
936                if pty.termios.echo {
937                    deliver_output(pty, waiters, b"\r\n", true)?;
938                }
939                deliver_input(pty, waiters, &line)?;
940                pty.line_buffer.clear();
941                continue;
942            }
943
944            if pty.line_buffer.len() >= MAX_CANON {
945                continue;
946            }
947            if pty.termios.echo {
948                deliver_output(pty, waiters, &[byte], true)?;
949            }
950            pty.line_buffer.push(byte);
951        } else {
952            if pty.termios.echo {
953                deliver_output(pty, waiters, &[byte], true)?;
954            }
955            deliver_input(pty, waiters, &[byte])?;
956        }
957    }
958
959    Ok(())
960}
961
962fn translate_input(termios: &Termios, data: &[u8]) -> Vec<u8> {
963    if !termios.icrnl || !data.contains(&b'\r') {
964        return data.to_vec();
965    }
966
967    data.iter()
968        .map(|byte| if *byte == b'\r' { b'\n' } else { *byte })
969        .collect()
970}
971
972fn deliver_input(
973    pty: &mut PtyState,
974    waiters: &mut BTreeMap<u64, PendingRead>,
975    data: &[u8],
976) -> PtyResult<()> {
977    if let Some(waiter_id) = pty.waiting_input_reads.pop_front() {
978        if let Some(waiter) = waiters.get_mut(&waiter_id) {
979            if data.len() <= waiter.length {
980                waiter.result = Some(Some(data.to_vec()));
981            } else {
982                // The waiter consumes `waiter.length` bytes directly; only the
983                // tail is buffered, so the buffer cap must be enforced on the
984                // tail. Otherwise a single large write past a pending reader
985                // bypasses MAX_PTY_BUFFER_BYTES entirely.
986                let tail_len = data.len() - waiter.length;
987                if tail_len > available_capacity(&pty.input_buffer) {
988                    pty.waiting_input_reads.push_front(waiter_id);
989                    return Err(PtyError::would_block("PTY input buffer full"));
990                }
991                let (head, tail) = data.split_at(waiter.length);
992                waiter.result = Some(Some(head.to_vec()));
993                pty.input_buffer.push_front(tail.to_vec());
994            }
995            return Ok(());
996        }
997    }
998
999    if buffer_size(&pty.input_buffer).saturating_add(data.len()) > MAX_PTY_BUFFER_BYTES {
1000        return Err(PtyError::would_block("PTY input buffer full"));
1001    }
1002
1003    pty.input_buffer.push_back(data.to_vec());
1004    Ok(())
1005}
1006
1007fn deliver_output(
1008    pty: &mut PtyState,
1009    waiters: &mut BTreeMap<u64, PendingRead>,
1010    data: &[u8],
1011    echo: bool,
1012) -> PtyResult<()> {
1013    if let Some(waiter_id) = pty.waiting_output_reads.pop_front() {
1014        if let Some(waiter) = waiters.get_mut(&waiter_id) {
1015            if data.len() <= waiter.length {
1016                waiter.result = Some(Some(data.to_vec()));
1017            } else {
1018                // Enforce the buffer cap on the tail (see deliver_input).
1019                let tail_len = data.len() - waiter.length;
1020                if tail_len > available_capacity(&pty.output_buffer) {
1021                    pty.waiting_output_reads.push_front(waiter_id);
1022                    let message = if echo {
1023                        "PTY output buffer full (echo backpressure)"
1024                    } else {
1025                        "PTY output buffer full"
1026                    };
1027                    return Err(PtyError::would_block(message));
1028                }
1029                let (head, tail) = data.split_at(waiter.length);
1030                waiter.result = Some(Some(head.to_vec()));
1031                pty.output_buffer.push_front(tail.to_vec());
1032            }
1033            return Ok(());
1034        }
1035    }
1036
1037    if buffer_size(&pty.output_buffer).saturating_add(data.len()) > MAX_PTY_BUFFER_BYTES {
1038        let message = if echo {
1039            "PTY output buffer full (echo backpressure)"
1040        } else {
1041            "PTY output buffer full"
1042        };
1043        return Err(PtyError::would_block(message));
1044    }
1045
1046    pty.output_buffer.push_back(data.to_vec());
1047    Ok(())
1048}
1049
1050fn signal_for_byte(termios: &Termios, byte: u8) -> Option<i32> {
1051    if byte == termios.cc.vintr {
1052        return Some(SIGINT);
1053    }
1054    if byte == termios.cc.vquit {
1055        return Some(SIGQUIT);
1056    }
1057    if byte == termios.cc.vsusp {
1058        return Some(SIGTSTP);
1059    }
1060    None
1061}
1062
1063fn buffer_size(buffer: &VecDeque<Vec<u8>>) -> usize {
1064    buffer.iter().map(Vec::len).sum()
1065}
1066
1067fn available_capacity(buffer: &VecDeque<Vec<u8>>) -> usize {
1068    MAX_PTY_BUFFER_BYTES.saturating_sub(buffer_size(buffer))
1069}
1070
1071fn drain_buffer(buffer: &mut VecDeque<Vec<u8>>, length: usize) -> Vec<u8> {
1072    let mut chunks = Vec::new();
1073    let mut remaining = length;
1074
1075    while remaining > 0 {
1076        let Some(chunk) = buffer.pop_front() else {
1077            break;
1078        };
1079        if chunk.len() <= remaining {
1080            remaining -= chunk.len();
1081            chunks.push(chunk);
1082        } else {
1083            let (head, tail) = chunk.split_at(remaining);
1084            chunks.push(head.to_vec());
1085            buffer.push_front(tail.to_vec());
1086            remaining = 0;
1087        }
1088    }
1089
1090    if chunks.len() == 1 {
1091        return chunks.pop().expect("single chunk should exist");
1092    }
1093
1094    let total = chunks.iter().map(Vec::len).sum();
1095    let mut result = Vec::with_capacity(total);
1096    for chunk in chunks {
1097        result.extend_from_slice(&chunk);
1098    }
1099    result
1100}
1101
1102fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
1103    match mutex.lock() {
1104        Ok(guard) => guard,
1105        Err(poisoned) => poisoned.into_inner(),
1106    }
1107}
1108
1109fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
1110    match condvar.wait(guard) {
1111        Ok(guard) => guard,
1112        Err(poisoned) => poisoned.into_inner(),
1113    }
1114}
1115
1116fn wait_timeout_or_recover<'a, T>(
1117    condvar: &Condvar,
1118    guard: MutexGuard<'a, T>,
1119    timeout: Duration,
1120) -> (MutexGuard<'a, T>, std::sync::WaitTimeoutResult) {
1121    match condvar.wait_timeout(guard, timeout) {
1122        Ok(result) => result,
1123        Err(poisoned) => poisoned.into_inner(),
1124    }
1125}