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