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 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 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 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 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}