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 result: Option<Option<Vec<u8>>>,
220}
221
222#[derive(Debug, Clone, Default)]
223struct PtyState {
224 path: String,
225 input_buffer: VecDeque<Vec<u8>>,
226 output_buffer: VecDeque<Vec<u8>>,
227 closed_master: bool,
228 closed_slave: bool,
229 waiting_input_reads: VecDeque<u64>,
230 waiting_output_reads: VecDeque<u64>,
231 termios: Termios,
232 line_buffer: Vec<u8>,
233 foreground_pgid: u32,
234 window_size: PtyWindowSize,
235}
236
237#[derive(Debug)]
238struct PtyManagerState {
239 ptys: BTreeMap<u64, PtyState>,
240 desc_to_pty: BTreeMap<u64, PtyRef>,
241 waiters: BTreeMap<u64, PendingRead>,
242 next_pty_id: u64,
243 next_desc_id: u64,
244 next_waiter_id: u64,
245}
246
247impl Default for PtyManagerState {
248 fn default() -> Self {
249 Self {
250 ptys: BTreeMap::new(),
251 desc_to_pty: BTreeMap::new(),
252 waiters: BTreeMap::new(),
253 next_pty_id: 0,
254 next_desc_id: 200_000,
255 next_waiter_id: 1,
256 }
257 }
258}
259
260#[derive(Debug)]
261struct PtyManagerInner {
262 state: Mutex<PtyManagerState>,
263 waiters: Condvar,
264}
265
266#[derive(Clone)]
267pub struct PtyManager {
268 inner: Arc<PtyManagerInner>,
269 on_signal: Option<SignalHandler>,
270 notifier: Option<PollNotifier>,
271}
272
273impl Default for PtyManager {
274 fn default() -> Self {
275 Self {
276 inner: Arc::new(PtyManagerInner {
277 state: Mutex::new(PtyManagerState::default()),
278 waiters: Condvar::new(),
279 }),
280 on_signal: None,
281 notifier: None,
282 }
283 }
284}
285
286impl PtyManager {
287 pub fn new() -> Self {
288 Self::default()
289 }
290
291 pub fn with_signal_handler(on_signal: SignalHandler) -> Self {
292 let mut manager = Self::new();
293 manager.on_signal = Some(on_signal);
294 manager
295 }
296
297 pub(crate) fn with_signal_handler_and_notifier(
298 on_signal: SignalHandler,
299 notifier: PollNotifier,
300 ) -> Self {
301 let mut manager = Self::with_notifier(notifier);
302 manager.on_signal = Some(on_signal);
303 manager
304 }
305
306 pub(crate) fn with_notifier(notifier: PollNotifier) -> Self {
307 Self {
308 notifier: Some(notifier),
309 ..Self::default()
310 }
311 }
312
313 pub fn create_pty(&self) -> PtyPair {
314 let mut state = lock_or_recover(&self.inner.state);
315 let pty_id = state.next_pty_id;
316 state.next_pty_id += 1;
317
318 let master_id = state.next_desc_id;
319 state.next_desc_id += 1;
320 let slave_id = state.next_desc_id;
321 state.next_desc_id += 1;
322
323 let path = format!("/dev/pts/{pty_id}");
324 state.ptys.insert(
325 pty_id,
326 PtyState {
327 path: path.clone(),
328 termios: Termios::default(),
329 window_size: PtyWindowSize::default(),
330 ..PtyState::default()
331 },
332 );
333 state.desc_to_pty.insert(
334 master_id,
335 PtyRef {
336 pty_id,
337 end: PtyEndKind::Master,
338 },
339 );
340 state.desc_to_pty.insert(
341 slave_id,
342 PtyRef {
343 pty_id,
344 end: PtyEndKind::Slave,
345 },
346 );
347 drop(state);
348
349 PtyPair {
350 master: PtyEnd {
351 description: Arc::new(FileDescription::with_ref_count(
352 master_id,
353 format!("pty:{pty_id}:master"),
354 O_RDWR,
355 0,
356 )),
357 filetype: FILETYPE_CHARACTER_DEVICE,
358 },
359 slave: PtyEnd {
360 description: Arc::new(FileDescription::with_ref_count(
361 slave_id,
362 path.clone(),
363 O_RDWR,
364 0,
365 )),
366 filetype: FILETYPE_CHARACTER_DEVICE,
367 },
368 path,
369 }
370 }
371
372 pub fn create_pty_fds(&self, fd_table: &mut ProcessFdTable) -> FdResult<(u32, u32, String)> {
373 let pty = self.create_pty();
374 let master_fd = fd_table.open_with(
375 Arc::clone(&pty.master.description),
376 FILETYPE_CHARACTER_DEVICE,
377 None,
378 )?;
379 match fd_table.open_with(
380 Arc::clone(&pty.slave.description),
381 FILETYPE_CHARACTER_DEVICE,
382 None,
383 ) {
384 Ok(slave_fd) => Ok((master_fd, slave_fd, pty.path)),
385 Err(error) => {
386 fd_table.close(master_fd);
387 self.close(pty.master.description.id());
388 self.close(pty.slave.description.id());
389 Err(error)
390 }
391 }
392 }
393
394 pub fn poll(&self, description_id: u64, requested: PollEvents) -> PtyResult<PollEvents> {
395 let state = lock_or_recover(&self.inner.state);
396 let pty_ref = state
397 .desc_to_pty
398 .get(&description_id)
399 .copied()
400 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
401 let pty = state
402 .ptys
403 .get(&pty_ref.pty_id)
404 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
405
406 let mut events = PollEvents::empty();
407 match pty_ref.end {
408 PtyEndKind::Master => {
409 if requested.intersects(POLLIN) && !pty.output_buffer.is_empty() {
410 events |= POLLIN;
411 }
412 if pty.closed_slave {
413 events |= POLLHUP;
414 } else if requested.intersects(POLLOUT)
415 && (available_capacity(&pty.input_buffer) > 0
416 || !pty.waiting_input_reads.is_empty())
417 {
418 events |= POLLOUT;
419 }
420 }
421 PtyEndKind::Slave => {
422 if requested.intersects(POLLIN) && !pty.input_buffer.is_empty() {
423 events |= POLLIN;
424 }
425 if pty.closed_master {
426 events |= POLLHUP;
427 } else if requested.intersects(POLLOUT)
428 && (available_capacity(&pty.output_buffer) > 0
429 || !pty.waiting_output_reads.is_empty())
430 {
431 events |= POLLOUT;
432 }
433 }
434 }
435
436 Ok(events)
437 }
438
439 pub fn write(&self, description_id: u64, data: impl AsRef<[u8]>) -> PtyResult<usize> {
440 let payload = data.as_ref();
441 let mut signals = Vec::new();
442
443 {
444 let mut state = lock_or_recover(&self.inner.state);
445 let pty_ref = state
446 .desc_to_pty
447 .get(&description_id)
448 .copied()
449 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
450 let PtyManagerState { ptys, waiters, .. } = &mut *state;
451 let pty = ptys
452 .get_mut(&pty_ref.pty_id)
453 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
454
455 match pty_ref.end {
456 PtyEndKind::Master => {
457 if pty.closed_master {
458 return Err(PtyError::io("master closed"));
459 }
460 if pty.closed_slave {
461 return Err(PtyError::io("slave closed"));
462 }
463 process_input(pty, waiters, payload, &mut signals)?;
464 }
465 PtyEndKind::Slave => {
466 if pty.closed_slave {
467 return Err(PtyError::io("slave closed"));
468 }
469 if pty.closed_master {
470 return Err(PtyError::io("master closed"));
471 }
472
473 let processed = process_output(&pty.termios, payload);
474 deliver_output(pty, waiters, &processed, false)?;
475 }
476 }
477 }
478
479 self.notify_waiters_and_pollers();
480 if let Some(on_signal) = &self.on_signal {
481 for (pgid, signal) in signals {
482 if pgid > 0 {
483 on_signal(pgid, signal);
484 }
485 }
486 }
487
488 Ok(payload.len())
489 }
490
491 pub fn read(&self, description_id: u64, length: usize) -> PtyResult<Option<Vec<u8>>> {
492 self.read_with_timeout(description_id, length, None)
493 }
494
495 pub fn read_with_timeout(
496 &self,
497 description_id: u64,
498 length: usize,
499 timeout: Option<Duration>,
500 ) -> PtyResult<Option<Vec<u8>>> {
501 let mut state = lock_or_recover(&self.inner.state);
502 let pty_ref = state
503 .desc_to_pty
504 .get(&description_id)
505 .copied()
506 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
507 let mut waiter_id = None;
508 let deadline = timeout.map(|duration| Instant::now() + duration);
509
510 loop {
511 if let Some(id) = waiter_id {
512 if let Some(waiter) = state.waiters.get_mut(&id) {
513 if let Some(result) = waiter.result.take() {
514 state.waiters.remove(&id);
515 return Ok(result);
516 }
517 }
518 }
519
520 {
521 let pty = state
522 .ptys
523 .get_mut(&pty_ref.pty_id)
524 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
525
526 match pty_ref.end {
527 PtyEndKind::Master => {
528 if pty.closed_master {
529 if let Some(id) = waiter_id {
530 state.waiters.remove(&id);
531 }
532 return Err(PtyError::io("master closed"));
533 }
534
535 if !pty.output_buffer.is_empty() {
536 let result = drain_buffer(&mut pty.output_buffer, length);
537 self.notify_waiters_and_pollers();
538 return Ok(Some(result));
539 }
540
541 if pty.closed_slave {
542 if let Some(id) = waiter_id {
543 state.waiters.remove(&id);
544 }
545 return Ok(None);
546 }
547 }
548 PtyEndKind::Slave => {
549 if pty.closed_slave {
550 if let Some(id) = waiter_id {
551 state.waiters.remove(&id);
552 }
553 return Err(PtyError::io("slave closed"));
554 }
555
556 if !pty.input_buffer.is_empty() {
557 let result = drain_buffer(&mut pty.input_buffer, length);
558 self.notify_waiters_and_pollers();
559 return Ok(Some(result));
560 }
561
562 if pty.closed_master {
563 if let Some(id) = waiter_id {
564 state.waiters.remove(&id);
565 }
566 return Ok(None);
567 }
568 }
569 }
570 }
571
572 let id = if let Some(id) = waiter_id {
573 id
574 } else {
575 let next = state.next_waiter_id;
576 state.next_waiter_id += 1;
577 state.waiters.insert(next, PendingRead::default());
578 let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) else {
579 state.waiters.remove(&next);
580 return Err(PtyError::bad_file_descriptor("PTY not found"));
581 };
582 match pty_ref.end {
583 PtyEndKind::Master => pty.waiting_output_reads.push_back(next),
584 PtyEndKind::Slave => pty.waiting_input_reads.push_back(next),
585 }
586 self.notify_waiters_and_pollers();
587 waiter_id = Some(next);
588 next
589 };
590
591 let Some(deadline) = deadline else {
592 state = wait_or_recover(&self.inner.waiters, state);
593 if !state.waiters.contains_key(&id) {
594 waiter_id = None;
595 }
596 continue;
597 };
598
599 let now = Instant::now();
600 if now >= deadline {
601 if let Some(id) = waiter_id.take() {
602 state.waiters.remove(&id);
603 if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
604 pty.waiting_input_reads.retain(|queued| *queued != id);
605 pty.waiting_output_reads.retain(|queued| *queued != id);
606 }
607 self.notify_waiters_and_pollers();
608 }
609 return Err(PtyError::would_block("PTY read timed out"));
610 }
611
612 let remaining = deadline.saturating_duration_since(now);
613 let (next_state, wait_result) =
614 wait_timeout_or_recover(&self.inner.waiters, state, remaining);
615 state = next_state;
616 if !state.waiters.contains_key(&id) {
617 waiter_id = None;
618 }
619 if wait_result.timed_out() {
620 if let Some(id) = waiter_id.take() {
621 state.waiters.remove(&id);
622 if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
623 pty.waiting_input_reads.retain(|queued| *queued != id);
624 pty.waiting_output_reads.retain(|queued| *queued != id);
625 }
626 self.notify_waiters_and_pollers();
627 }
628 return Err(PtyError::would_block("PTY read timed out"));
629 }
630 }
631 }
632
633 pub fn close(&self, description_id: u64) {
634 let mut state = lock_or_recover(&self.inner.state);
635 let Some(pty_ref) = state.desc_to_pty.remove(&description_id) else {
636 return;
637 };
638
639 let (waiter_ids, remove_pty) = if let Some(pty) = state.ptys.get_mut(&pty_ref.pty_id) {
640 match pty_ref.end {
641 PtyEndKind::Master => {
642 pty.closed_master = true;
643 let mut waiters = pty.waiting_input_reads.drain(..).collect::<Vec<_>>();
644 waiters.extend(pty.waiting_output_reads.drain(..));
645 (waiters, pty.closed_master && pty.closed_slave)
646 }
647 PtyEndKind::Slave => {
648 pty.closed_slave = true;
649 let mut waiters = pty.waiting_output_reads.drain(..).collect::<Vec<_>>();
650 waiters.extend(pty.waiting_input_reads.drain(..));
651 (waiters, pty.closed_master && pty.closed_slave)
652 }
653 }
654 } else {
655 (Vec::new(), false)
656 };
657
658 for waiter_id in waiter_ids {
659 if let Some(waiter) = state.waiters.get_mut(&waiter_id) {
660 waiter.result = Some(None);
661 }
662 }
663
664 if remove_pty {
665 state.ptys.remove(&pty_ref.pty_id);
666 }
667 self.notify_waiters_and_pollers();
668 }
669
670 pub fn is_pty(&self, description_id: u64) -> bool {
671 lock_or_recover(&self.inner.state)
672 .desc_to_pty
673 .contains_key(&description_id)
674 }
675
676 pub fn is_slave(&self, description_id: u64) -> bool {
677 lock_or_recover(&self.inner.state)
678 .desc_to_pty
679 .get(&description_id)
680 .map(|pty_ref| pty_ref.end == PtyEndKind::Slave)
681 .unwrap_or(false)
682 }
683
684 pub fn set_discipline(
685 &self,
686 description_id: u64,
687 config: LineDisciplineConfig,
688 ) -> PtyResult<()> {
689 let mut state = lock_or_recover(&self.inner.state);
690 let pty_ref = state
691 .desc_to_pty
692 .get(&description_id)
693 .copied()
694 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
695 let pty = state
696 .ptys
697 .get_mut(&pty_ref.pty_id)
698 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
699 if let Some(canonical) = config.canonical {
700 pty.termios.icanon = canonical;
701 }
702 if let Some(echo) = config.echo {
703 pty.termios.echo = echo;
704 }
705 if let Some(isig) = config.isig {
706 pty.termios.isig = isig;
707 }
708 Ok(())
709 }
710
711 pub fn get_termios(&self, description_id: u64) -> PtyResult<Termios> {
712 let 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 state
719 .ptys
720 .get(&pty_ref.pty_id)
721 .cloned()
722 .map(|pty| pty.termios)
723 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))
724 }
725
726 pub fn set_termios(&self, description_id: u64, termios: PartialTermios) -> PtyResult<()> {
727 let mut state = lock_or_recover(&self.inner.state);
728 let pty_ref = state
729 .desc_to_pty
730 .get(&description_id)
731 .copied()
732 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
733 let pty = state
734 .ptys
735 .get_mut(&pty_ref.pty_id)
736 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
737 pty.termios.merge(termios);
738 Ok(())
739 }
740
741 pub fn set_foreground_pgid(&self, description_id: u64, pgid: u32) -> PtyResult<()> {
742 let mut state = lock_or_recover(&self.inner.state);
743 let pty_ref = state
744 .desc_to_pty
745 .get(&description_id)
746 .copied()
747 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
748 let pty = state
749 .ptys
750 .get_mut(&pty_ref.pty_id)
751 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
752 pty.foreground_pgid = pgid;
753 Ok(())
754 }
755
756 pub fn get_foreground_pgid(&self, description_id: u64) -> PtyResult<u32> {
757 let state = lock_or_recover(&self.inner.state);
758 let pty_ref = state
759 .desc_to_pty
760 .get(&description_id)
761 .copied()
762 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
763 state
764 .ptys
765 .get(&pty_ref.pty_id)
766 .map(|pty| pty.foreground_pgid)
767 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))
768 }
769
770 pub fn resize(&self, description_id: u64, cols: u16, rows: u16) -> PtyResult<Option<u32>> {
771 let mut state = lock_or_recover(&self.inner.state);
772 let pty_ref = state
773 .desc_to_pty
774 .get(&description_id)
775 .copied()
776 .ok_or_else(|| PtyError::bad_file_descriptor("not a PTY end"))?;
777 let pty = state
778 .ptys
779 .get_mut(&pty_ref.pty_id)
780 .ok_or_else(|| PtyError::bad_file_descriptor("PTY not found"))?;
781 let next_size = PtyWindowSize { cols, rows };
782 if pty.window_size == next_size {
783 return Ok(None);
784 }
785 pty.window_size = next_size;
786 Ok((pty.foreground_pgid > 0).then_some(pty.foreground_pgid))
787 }
788
789 pub fn pty_count(&self) -> usize {
790 lock_or_recover(&self.inner.state).ptys.len()
791 }
792
793 pub fn buffered_input_bytes(&self) -> usize {
794 lock_or_recover(&self.inner.state)
795 .ptys
796 .values()
797 .map(|pty| buffer_size(&pty.input_buffer))
798 .sum()
799 }
800
801 pub fn buffered_output_bytes(&self) -> usize {
802 lock_or_recover(&self.inner.state)
803 .ptys
804 .values()
805 .map(|pty| buffer_size(&pty.output_buffer))
806 .sum()
807 }
808
809 pub fn path_for(&self, description_id: u64) -> Option<String> {
810 let state = lock_or_recover(&self.inner.state);
811 let pty_ref = state.desc_to_pty.get(&description_id)?;
812 state.ptys.get(&pty_ref.pty_id).map(|pty| pty.path.clone())
813 }
814
815 fn notify_waiters_and_pollers(&self) {
816 self.inner.waiters.notify_all();
817 if let Some(notifier) = &self.notifier {
818 notifier.notify();
819 }
820 }
821}
822
823fn process_output(termios: &Termios, data: &[u8]) -> Vec<u8> {
824 if !termios.opost || !termios.onlcr || !data.contains(&b'\n') {
825 return data.to_vec();
826 }
827
828 let extra_crs = data
829 .iter()
830 .enumerate()
831 .filter(|(index, byte)| **byte == b'\n' && (*index == 0 || data[*index - 1] != b'\r'))
832 .count();
833 if extra_crs == 0 {
834 return data.to_vec();
835 }
836
837 let mut result = Vec::with_capacity(data.len() + extra_crs);
838 for (index, byte) in data.iter().enumerate() {
839 if *byte == b'\n' && (index == 0 || data[index - 1] != b'\r') {
840 result.push(b'\r');
841 }
842 result.push(*byte);
843 }
844 result
845}
846
847fn process_input(
848 pty: &mut PtyState,
849 waiters: &mut BTreeMap<u64, PendingRead>,
850 data: &[u8],
851 signals: &mut Vec<(u32, i32)>,
852) -> PtyResult<()> {
853 if !pty.termios.icanon && !pty.termios.echo && !pty.termios.isig {
854 let translated = translate_input(&pty.termios, data);
855 deliver_input(pty, waiters, &translated)?;
856 return Ok(());
857 }
858
859 for mut byte in data.iter().copied() {
860 if pty.termios.icrnl && byte == b'\r' {
861 byte = b'\n';
862 }
863
864 if pty.termios.isig {
865 if let Some(signal) = signal_for_byte(&pty.termios, byte) {
866 if pty.termios.icanon {
867 pty.line_buffer.clear();
868 }
869 if pty.foreground_pgid > 0 {
870 signals.push((pty.foreground_pgid, signal));
871 }
872 continue;
873 }
874 }
875
876 if pty.termios.icanon {
877 if byte == pty.termios.cc.veof {
878 if pty.line_buffer.is_empty() {
879 deliver_input(pty, waiters, &[])?;
880 } else {
881 let line = pty.line_buffer.clone();
882 deliver_input(pty, waiters, &line)?;
883 pty.line_buffer.clear();
884 }
885 continue;
886 }
887
888 if byte == pty.termios.cc.verase || byte == 0x08 {
889 if !pty.line_buffer.is_empty() {
890 pty.line_buffer.pop();
891 if pty.termios.echo {
892 deliver_output(pty, waiters, &[0x08, 0x20, 0x08], true)?;
893 }
894 }
895 continue;
896 }
897
898 if byte == b'\n' {
899 pty.line_buffer.push(b'\n');
900 if pty.termios.echo {
901 deliver_output(pty, waiters, &[b'\r', b'\n'], true)?;
902 }
903 let line = pty.line_buffer.clone();
904 deliver_input(pty, waiters, &line)?;
905 pty.line_buffer.clear();
906 continue;
907 }
908
909 if pty.line_buffer.len() >= MAX_CANON {
910 continue;
911 }
912 pty.line_buffer.push(byte);
913 if pty.termios.echo {
914 deliver_output(pty, waiters, &[byte], true)?;
915 }
916 } else {
917 if pty.termios.echo {
918 deliver_output(pty, waiters, &[byte], true)?;
919 }
920 deliver_input(pty, waiters, &[byte])?;
921 }
922 }
923
924 Ok(())
925}
926
927fn translate_input(termios: &Termios, data: &[u8]) -> Vec<u8> {
928 if !termios.icrnl || !data.contains(&b'\r') {
929 return data.to_vec();
930 }
931
932 data.iter()
933 .map(|byte| if *byte == b'\r' { b'\n' } else { *byte })
934 .collect()
935}
936
937fn deliver_input(
938 pty: &mut PtyState,
939 waiters: &mut BTreeMap<u64, PendingRead>,
940 data: &[u8],
941) -> PtyResult<()> {
942 if let Some(waiter_id) = pty.waiting_input_reads.pop_front() {
943 if let Some(waiter) = waiters.get_mut(&waiter_id) {
944 waiter.result = Some(Some(data.to_vec()));
945 return Ok(());
946 }
947 }
948
949 if buffer_size(&pty.input_buffer).saturating_add(data.len()) > MAX_PTY_BUFFER_BYTES {
950 return Err(PtyError::would_block("PTY input buffer full"));
951 }
952
953 pty.input_buffer.push_back(data.to_vec());
954 Ok(())
955}
956
957fn deliver_output(
958 pty: &mut PtyState,
959 waiters: &mut BTreeMap<u64, PendingRead>,
960 data: &[u8],
961 echo: bool,
962) -> PtyResult<()> {
963 if let Some(waiter_id) = pty.waiting_output_reads.pop_front() {
964 if let Some(waiter) = waiters.get_mut(&waiter_id) {
965 waiter.result = Some(Some(data.to_vec()));
966 return Ok(());
967 }
968 }
969
970 if buffer_size(&pty.output_buffer).saturating_add(data.len()) > MAX_PTY_BUFFER_BYTES {
971 let message = if echo {
972 "PTY output buffer full (echo backpressure)"
973 } else {
974 "PTY output buffer full"
975 };
976 return Err(PtyError::would_block(message));
977 }
978
979 pty.output_buffer.push_back(data.to_vec());
980 Ok(())
981}
982
983fn signal_for_byte(termios: &Termios, byte: u8) -> Option<i32> {
984 if byte == termios.cc.vintr {
985 return Some(SIGINT);
986 }
987 if byte == termios.cc.vquit {
988 return Some(SIGQUIT);
989 }
990 if byte == termios.cc.vsusp {
991 return Some(SIGTSTP);
992 }
993 None
994}
995
996fn buffer_size(buffer: &VecDeque<Vec<u8>>) -> usize {
997 buffer.iter().map(Vec::len).sum()
998}
999
1000fn available_capacity(buffer: &VecDeque<Vec<u8>>) -> usize {
1001 MAX_PTY_BUFFER_BYTES.saturating_sub(buffer_size(buffer))
1002}
1003
1004fn drain_buffer(buffer: &mut VecDeque<Vec<u8>>, length: usize) -> Vec<u8> {
1005 let mut chunks = Vec::new();
1006 let mut remaining = length;
1007
1008 while remaining > 0 {
1009 let Some(chunk) = buffer.pop_front() else {
1010 break;
1011 };
1012 if chunk.len() <= remaining {
1013 remaining -= chunk.len();
1014 chunks.push(chunk);
1015 } else {
1016 let (head, tail) = chunk.split_at(remaining);
1017 chunks.push(head.to_vec());
1018 buffer.push_front(tail.to_vec());
1019 remaining = 0;
1020 }
1021 }
1022
1023 if chunks.len() == 1 {
1024 return chunks.pop().expect("single chunk should exist");
1025 }
1026
1027 let total = chunks.iter().map(Vec::len).sum();
1028 let mut result = Vec::with_capacity(total);
1029 for chunk in chunks {
1030 result.extend_from_slice(&chunk);
1031 }
1032 result
1033}
1034
1035fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
1036 match mutex.lock() {
1037 Ok(guard) => guard,
1038 Err(poisoned) => poisoned.into_inner(),
1039 }
1040}
1041
1042fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
1043 match condvar.wait(guard) {
1044 Ok(guard) => guard,
1045 Err(poisoned) => poisoned.into_inner(),
1046 }
1047}
1048
1049fn wait_timeout_or_recover<'a, T>(
1050 condvar: &Condvar,
1051 guard: MutexGuard<'a, T>,
1052 timeout: Duration,
1053) -> (MutexGuard<'a, T>, std::sync::WaitTimeoutResult) {
1054 match condvar.wait_timeout(guard, timeout) {
1055 Ok(result) => result,
1056 Err(poisoned) => poisoned.into_inner(),
1057 }
1058}