1use std::collections::VecDeque;
2use std::io::Write as _;
3use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
4use std::sync::{Arc, Condvar, Mutex};
5use std::thread;
6use std::time::{Duration, Instant};
7
8use super::backend::PtySlave;
9use super::backend::{Backend, PtyBackend, PtyChild, PtyMaster, PtySize};
10use super::{
11 is_ignorable_process_control_error, poll_pty_process, record_pty_input_metrics,
12 spawn_pty_reader, store_pty_returncode, terminal_input_relay_worker, write_pty_input,
13 IdleDetectorCore, NativePtyHandles, PtyError, PtyReadShared, PtyReadState,
14};
15use running_process_platform_internal::platform::terminal as pty_platform;
16
17pub struct NativePtyProcess {
23 pub argv: Vec<String>,
25 pub cwd: Option<String>,
27 pub env: Option<Vec<(String, String)>>,
29 pub rows: u16,
31 pub cols: u16,
33 pub nice: Option<i32>,
35 pub handles: Arc<Mutex<Option<NativePtyHandles>>>,
37 pub reader: Arc<PtyReadShared>,
39 pub returncode: Arc<Mutex<Option<i32>>>,
41 pub input_bytes_total: Arc<AtomicUsize>,
43 pub newline_events_total: Arc<AtomicUsize>,
45 pub submit_events_total: Arc<AtomicUsize>,
47 pub echo: Arc<AtomicBool>,
49 pub idle_detector: Arc<Mutex<Option<Arc<IdleDetectorCore>>>>,
51 pub output_bytes_total: Arc<AtomicUsize>,
53 pub control_churn_bytes_total: Arc<AtomicUsize>,
55 pub reader_worker: Mutex<Option<thread::JoinHandle<()>>>,
57 pub terminal_input_relay_stop: Arc<AtomicBool>,
59 pub terminal_input_relay_active: Arc<AtomicBool>,
61 pub terminal_input_relay_worker: Mutex<Option<thread::JoinHandle<()>>>,
63}
64
65pub(super) fn resolved_spawn_cwd(cwd: Option<&str>) -> Option<String> {
66 cwd.map(str::to_owned).or_else(|| {
67 std::env::current_dir()
68 .ok()
69 .map(|cwd| cwd.to_string_lossy().to_string())
70 })
71}
72
73impl NativePtyProcess {
74 pub fn new(
78 argv: Vec<String>,
79 cwd: Option<String>,
80 env: Option<Vec<(String, String)>>,
81 rows: u16,
82 cols: u16,
83 nice: Option<i32>,
84 ) -> Result<Self, PtyError> {
85 if argv.is_empty() {
86 return Err(PtyError::Other("command cannot be empty".into()));
87 }
88 Ok(Self {
89 argv,
90 cwd,
91 env,
92 rows,
93 cols,
94 nice,
95 handles: Arc::new(Mutex::new(None)),
96 reader: Arc::new(PtyReadShared {
97 state: Mutex::new(PtyReadState {
98 chunks: VecDeque::new(),
99 closed: false,
100 }),
101 condvar: Condvar::new(),
102 }),
103 returncode: Arc::new(Mutex::new(None)),
104 input_bytes_total: Arc::new(AtomicUsize::new(0)),
105 newline_events_total: Arc::new(AtomicUsize::new(0)),
106 submit_events_total: Arc::new(AtomicUsize::new(0)),
107 echo: Arc::new(AtomicBool::new(false)),
108 idle_detector: Arc::new(Mutex::new(None)),
109 output_bytes_total: Arc::new(AtomicUsize::new(0)),
110 control_churn_bytes_total: Arc::new(AtomicUsize::new(0)),
111 reader_worker: Mutex::new(None),
112 terminal_input_relay_stop: Arc::new(AtomicBool::new(false)),
113 terminal_input_relay_active: Arc::new(AtomicBool::new(false)),
114 terminal_input_relay_worker: Mutex::new(None),
115 })
116 }
117
118 pub fn mark_reader_closed(&self) {
120 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
121 guard.closed = true;
122 self.reader.condvar.notify_all();
123 }
124
125 pub fn store_returncode(&self, code: i32) {
127 store_pty_returncode(&self.returncode, code);
128 }
129
130 pub fn record_input_metrics(&self, data: &[u8], submit: bool) {
132 record_pty_input_metrics(
133 &self.input_bytes_total,
134 &self.newline_events_total,
135 &self.submit_events_total,
136 data,
137 submit,
138 );
139 }
140
141 pub fn write_impl(&self, data: &[u8], submit: bool) -> Result<(), PtyError> {
143 self.record_input_metrics(data, submit);
144 write_pty_input(&self.handles, data)?;
145 Ok(())
146 }
147
148 pub fn request_terminal_input_relay_stop(&self) {
150 self.terminal_input_relay_stop
151 .store(true, Ordering::Release);
152 self.terminal_input_relay_active
153 .store(false, Ordering::Release);
154 }
155
156 pub fn start_terminal_input_relay_impl(&self) -> Result<(), PtyError> {
158 let mut worker_guard = self
159 .terminal_input_relay_worker
160 .lock()
161 .expect("pty terminal input relay mutex poisoned");
162 if worker_guard.is_some() && self.terminal_input_relay_active() {
163 return Ok(());
164 }
165 if self
166 .handles
167 .lock()
168 .expect("pty handles mutex poisoned")
169 .is_none()
170 {
171 return Err(PtyError::NotRunning);
172 }
173
174 let Some(input) = pty_platform::TerminalInputSession::new().map_err(PtyError::Io)? else {
175 self.terminal_input_relay_active
176 .store(false, Ordering::Release);
177 return Ok(());
178 };
179
180 self.terminal_input_relay_stop
181 .store(false, Ordering::Release);
182 self.terminal_input_relay_active
183 .store(true, Ordering::Release);
184
185 let relay_state = super::TerminalInputRelayState {
186 handles: Arc::clone(&self.handles),
187 returncode: Arc::clone(&self.returncode),
188 input_bytes_total: Arc::clone(&self.input_bytes_total),
189 newline_events_total: Arc::clone(&self.newline_events_total),
190 submit_events_total: Arc::clone(&self.submit_events_total),
191 stop: Arc::clone(&self.terminal_input_relay_stop),
192 active: Arc::clone(&self.terminal_input_relay_active),
193 };
194
195 *worker_guard = Some(thread::spawn(move || {
196 terminal_input_relay_worker(input, relay_state);
197 }));
198 Ok(())
199 }
200
201 pub fn stop_terminal_input_relay_impl(&self) {
203 self.request_terminal_input_relay_stop();
204 if let Some(worker) = self
205 .terminal_input_relay_worker
206 .lock()
207 .expect("pty terminal input relay mutex poisoned")
208 .take()
209 {
210 let _ = worker.join();
211 }
212 }
213
214 pub fn terminal_input_relay_active(&self) -> bool {
216 self.terminal_input_relay_active.load(Ordering::Acquire)
217 }
218
219 #[inline(never)]
221 pub fn close_impl(&self) -> Result<(), PtyError> {
222 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::close_impl");
223 self.stop_terminal_input_relay_impl();
224 let mut guard = self.handles.lock().expect("pty handles mutex poisoned");
225 let Some(handles) = guard.take() else {
226 self.mark_reader_closed();
227 return Ok(());
228 };
229 drop(guard);
230
231 let NativePtyHandles {
232 master,
233 writer,
234 mut child,
235 process_guard,
236 } = handles;
237 let wait_before_close = pty_platform::wait_before_close_supported();
238 let mut control_error = None;
239 if wait_before_close {
240 if let Err(error) = pty_platform::kill_pty_process_group(master.as_ref()) {
241 if !is_ignorable_process_control_error(&error) {
242 control_error = Some(error);
243 }
244 }
245 if let Err(error) = child.kill() {
246 if !is_ignorable_process_control_error(&error) && control_error.is_none() {
247 control_error = Some(error);
248 }
249 }
250 }
251
252 drop(process_guard);
255 let reap_deadline = Instant::now() + Duration::from_secs(2);
256 let code = loop {
257 match child.try_wait() {
258 Ok(Some(status)) => break status as i32,
259 Ok(None) if Instant::now() < reap_deadline => {
260 thread::sleep(Duration::from_millis(10));
261 }
262 Ok(None) if wait_before_close => break -9,
263 Ok(None) => {
264 if let Err(error) = child.kill() {
265 if !is_ignorable_process_control_error(&error) && control_error.is_none() {
266 control_error = Some(error);
267 }
268 }
269 let kill_deadline = Instant::now() + Duration::from_secs(2);
270 break loop {
271 match child.try_wait() {
272 Ok(Some(status)) => break status as i32,
273 Ok(None) if Instant::now() < kill_deadline => {
274 thread::sleep(Duration::from_millis(10));
275 }
276 _ => break -9,
277 }
278 };
279 }
280 Err(error) => {
281 if control_error.is_none() {
282 control_error = Some(error);
283 }
284 break -9;
285 }
286 }
287 };
288 drop(writer);
289 let reader_worker = self
290 .reader_worker
291 .lock()
292 .expect("pty reader worker mutex poisoned")
293 .take();
294 let (teardown_tx, teardown_rx) = std::sync::mpsc::channel();
295 thread::spawn(move || {
296 drop(master);
297 drop(child);
298 if let Some(worker) = reader_worker {
299 let _ = worker.join();
300 }
301 let _ = teardown_tx.send(());
302 });
303 let _ = teardown_rx.recv_timeout(Duration::from_secs(2));
304 self.store_returncode(code);
305 self.mark_reader_closed();
306 control_error.map_or(Ok(()), |error| Err(PtyError::Io(error)))
307 }
308 #[inline(never)]
310 pub fn close_nonblocking(&self) {
311 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::close_nonblocking");
312 self.request_terminal_input_relay_stop();
313 let Ok(mut guard) = self.handles.lock() else {
314 return;
315 };
316 let Some(handles) = guard.take() else {
317 self.mark_reader_closed();
318 return;
319 };
320 drop(guard);
321
322 let NativePtyHandles {
323 master,
324 writer,
325 mut child,
326 process_guard,
327 } = handles;
328 let _ = child.kill();
329 drop(writer);
330 if pty_platform::wait_before_close_supported() {
331 drop(master);
332 drop(child);
333 drop(process_guard);
334 } else {
335 thread::spawn(move || {
336 drop(master);
337 drop(child);
338 drop(process_guard);
339 });
340 }
341 self.mark_reader_closed();
342 }
343 pub fn start_impl(&self) -> Result<(), PtyError> {
345 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::start");
346 let mut guard = self.handles.lock().expect("pty handles mutex poisoned");
347 if guard.is_some() {
348 return Err(PtyError::AlreadyStarted);
349 }
350
351 let spawn_context = pty_platform::before_pty_spawn();
352
353 let (mut master, slave) = Backend::openpty(PtySize {
354 rows: self.rows,
355 cols: self.cols,
356 pixel_width: 0,
357 pixel_height: 0,
358 })
359 .map_err(|e| PtyError::Spawn(e.to_string()))?;
360
361 let argv: Vec<std::ffi::OsString> =
363 self.argv.iter().map(std::ffi::OsString::from).collect();
364 let cwd = resolved_spawn_cwd(self.cwd.as_deref());
365 let env: Option<Vec<(std::ffi::OsString, std::ffi::OsString)>> =
366 self.env.as_ref().map(|e| {
367 e.iter()
368 .map(|(k, v)| (std::ffi::OsString::from(k), std::ffi::OsString::from(v)))
369 .collect()
370 });
371
372 let reader = master
373 .try_clone_reader()
374 .map_err(|e| PtyError::Spawn(e.to_string()))?;
375 let writer = master
376 .take_writer()
377 .map_err(|e| PtyError::Spawn(e.to_string()))?;
378 let cwd_path = cwd.as_deref().map(std::path::Path::new);
379 let child = slave
380 .spawn(&argv, cwd_path, env.as_deref())
381 .map_err(|e| PtyError::Spawn(e.to_string()))?;
382 let process_guard = pty_platform::prepare_pty_child(spawn_context, &child, self.nice)
383 .map_err(PtyError::Io)?;
384 let shared = Arc::clone(&self.reader);
385 let echo = Arc::clone(&self.echo);
386 let idle_detector = Arc::clone(&self.idle_detector);
387 let output_bytes = Arc::clone(&self.output_bytes_total);
388 let churn_bytes = Arc::clone(&self.control_churn_bytes_total);
389 let reader_worker = thread::spawn(move || {
390 spawn_pty_reader(
391 reader,
392 shared,
393 echo,
394 idle_detector,
395 output_bytes,
396 churn_bytes,
397 );
398 });
399 *self
400 .reader_worker
401 .lock()
402 .expect("pty reader worker mutex poisoned") = Some(reader_worker);
403
404 *guard = Some(NativePtyHandles {
405 master: Box::new(master) as Box<dyn PtyMaster>,
406 writer: Arc::new(Mutex::new(writer)),
409 child: Box::new(child) as Box<dyn PtyChild>,
410 process_guard,
411 });
412 Ok(())
413 }
414
415 pub fn respond_to_queries_impl(&self, data: &[u8]) -> Result<(), PtyError> {
417 let responses = pty_platform::query_responses(data);
418 if responses.is_empty() {
419 return Ok(());
420 }
421 let writer = {
422 let guard = self.handles.lock().expect("pty handles mutex poisoned");
423 let handles = guard.as_ref().ok_or(PtyError::NotRunning)?;
424 Arc::clone(&handles.writer)
425 };
426 let mut writer = writer.lock().expect("pty writer mutex poisoned");
427 for response in responses {
428 writer.write_all(&response).map_err(PtyError::Io)?;
429 }
430 writer.flush().map_err(PtyError::Io)
431 }
432
433 pub fn resize_impl(&self, rows: u16, cols: u16) -> Result<(), PtyError> {
435 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::resize");
436 let guard = self.handles.lock().expect("pty handles mutex poisoned");
437 if let Some(handles) = guard.as_ref() {
438 pty_platform::resize_pty(
439 handles.master.as_ref(),
440 PtySize {
441 rows,
442 cols,
443 pixel_width: 0,
444 pixel_height: 0,
445 },
446 )
447 .map_err(|error| PtyError::Other(error.to_string()))?;
448 }
449 Ok(())
450 }
451
452 pub fn send_interrupt_impl(&self) -> Result<(), PtyError> {
454 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::send_interrupt");
455 let (target, writer) = {
456 let guard = self.handles.lock().expect("pty handles mutex poisoned");
457 let handles = guard.as_ref().ok_or(PtyError::NotRunning)?;
458 (
459 handles.master.interrupt_target().map_err(PtyError::Io)?,
460 Arc::clone(&handles.writer),
461 )
462 };
463 let wrote_input =
464 pty_platform::send_pty_interrupt(target, &writer).map_err(PtyError::Io)?;
465 if wrote_input {
466 self.record_input_metrics(&[0x03], false);
467 }
468 Ok(())
469 }
470
471 pub fn wait_impl(&self, timeout: Option<f64>) -> Result<i32, PtyError> {
475 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::wait");
476 if let Some(code) = *self
478 .returncode
479 .lock()
480 .expect("pty returncode mutex poisoned")
481 {
482 return Ok(code);
483 }
484 let start = Instant::now();
485 loop {
486 if let Some(code) = poll_pty_process(&self.handles, &self.returncode)? {
487 return Ok(code);
488 }
489 if timeout.is_some_and(|limit| start.elapsed() >= Duration::from_secs_f64(limit)) {
490 return Err(PtyError::Timeout);
491 }
492 thread::sleep(Duration::from_millis(10));
496 }
497 }
498
499 pub fn terminate_impl(&self) -> Result<(), PtyError> {
501 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::terminate");
502 let should_close = {
503 let mut guard = self.handles.lock().expect("pty handles mutex poisoned");
504 let handles = guard.as_mut().ok_or(PtyError::NotRunning)?;
505 let pid = handles.child.pid();
506 if pid == 0 {
507 return Err(PtyError::NotRunning);
508 }
509 pty_platform::terminate_pty_child(pid).map_err(PtyError::Io)?
510 };
511 if should_close {
512 self.close_impl()?;
513 }
514 Ok(())
515 }
516
517 pub fn kill_impl(&self) -> Result<(), PtyError> {
519 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::kill");
520 if self
521 .handles
522 .lock()
523 .expect("pty handles mutex poisoned")
524 .is_none()
525 {
526 return Err(PtyError::NotRunning);
527 }
528 self.close_impl()
529 }
530
531 pub fn terminate_tree_impl(&self) -> Result<(), PtyError> {
533 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::terminate_tree");
534 let Some(pid) = self.pid()? else {
535 return if self
536 .returncode
537 .lock()
538 .expect("pty returncode mutex poisoned")
539 .is_some()
540 {
541 Ok(())
542 } else {
543 Err(PtyError::NotRunning)
544 };
545 };
546 if pty_platform::signal_pty_tree(pid, false).map_err(PtyError::Io)? {
547 self.close_impl()?;
548 }
549 Ok(())
550 }
551
552 pub fn kill_tree_impl(&self) -> Result<(), PtyError> {
554 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::kill_tree");
555 let Some(pid) = self.pid()? else {
556 return if self
557 .returncode
558 .lock()
559 .expect("pty returncode mutex poisoned")
560 .is_some()
561 {
562 Ok(())
563 } else {
564 Err(PtyError::NotRunning)
565 };
566 };
567 if pty_platform::signal_pty_tree(pid, true).map_err(PtyError::Io)? {
568 self.close_impl()?;
569 }
570 Ok(())
571 }
572
573 pub fn pid(&self) -> Result<Option<u32>, PtyError> {
575 let guard = self.handles.lock().expect("pty handles mutex poisoned");
576 if let Some(handles) = guard.as_ref() {
577 return Ok(pty_platform::preferred_pty_pid(
578 handles.master.as_ref(),
579 handles.child.as_ref(),
580 ));
581 }
582 Ok(None)
583 }
584
585 pub fn read_chunk_impl(&self, timeout: Option<f64>) -> Result<Option<Vec<u8>>, PtyError> {
588 let deadline = timeout.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
589 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
590 loop {
591 if let Some(chunk) = guard.chunks.pop_front() {
592 return Ok(Some(chunk));
593 }
594 if guard.closed {
595 return Err(PtyError::Other("Pseudo-terminal stream is closed".into()));
596 }
597 match deadline {
598 Some(deadline) => {
599 let now = Instant::now();
600 if now >= deadline {
601 return Ok(None); }
603 let wait = deadline.saturating_duration_since(now);
604 let result = self
605 .reader
606 .condvar
607 .wait_timeout(guard, wait)
608 .expect("pty read mutex poisoned");
609 guard = result.0;
610 }
611 None => {
612 guard = self
613 .reader
614 .condvar
615 .wait(guard)
616 .expect("pty read mutex poisoned");
617 }
618 }
619 }
620 }
621
622 pub fn wait_for_reader_closed_impl(&self, timeout: Option<f64>) -> bool {
624 let deadline = timeout.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
625 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
626 loop {
627 if guard.closed {
628 return true;
629 }
630 match deadline {
631 Some(deadline) => {
632 let now = Instant::now();
633 if now >= deadline {
634 return false;
635 }
636 let wait = deadline.saturating_duration_since(now);
637 let result = self
638 .reader
639 .condvar
640 .wait_timeout(guard, wait)
641 .expect("pty read mutex poisoned");
642 guard = result.0;
643 }
644 None => {
645 guard = self
646 .reader
647 .condvar
648 .wait(guard)
649 .expect("pty read mutex poisoned");
650 }
651 }
652 }
653 }
654
655 pub fn wait_and_drain_impl(
657 &self,
658 timeout: Option<f64>,
659 drain_timeout: f64,
660 ) -> Result<i32, PtyError> {
661 let code = self.wait_impl(timeout)?;
662 let deadline = Instant::now() + Duration::from_secs_f64(drain_timeout.max(0.0));
663 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
664 while !guard.closed {
665 let remaining = deadline.saturating_duration_since(Instant::now());
666 if remaining.is_zero() {
667 break;
668 }
669 let result = self
670 .reader
671 .condvar
672 .wait_timeout(guard, remaining)
673 .expect("pty read mutex poisoned");
674 guard = result.0;
675 }
676 Ok(code)
677 }
678
679 pub fn set_echo(&self, enabled: bool) {
681 self.echo.store(enabled, Ordering::Release);
682 }
683
684 pub fn echo_enabled(&self) -> bool {
686 self.echo.load(Ordering::Acquire)
687 }
688
689 pub fn attach_idle_detector(&self, detector: &Arc<IdleDetectorCore>) {
691 let mut guard = self
692 .idle_detector
693 .lock()
694 .expect("idle detector mutex poisoned");
695 *guard = Some(Arc::clone(detector));
696 }
697
698 pub fn detach_idle_detector(&self) {
700 let mut guard = self
701 .idle_detector
702 .lock()
703 .expect("idle detector mutex poisoned");
704 *guard = None;
705 }
706
707 pub fn pty_input_bytes_total(&self) -> usize {
709 self.input_bytes_total.load(Ordering::Acquire)
710 }
711
712 pub fn pty_newline_events_total(&self) -> usize {
714 self.newline_events_total.load(Ordering::Acquire)
715 }
716
717 pub fn pty_submit_events_total(&self) -> usize {
719 self.submit_events_total.load(Ordering::Acquire)
720 }
721
722 pub fn pty_output_bytes_total(&self) -> usize {
724 self.output_bytes_total.load(Ordering::Acquire)
725 }
726
727 pub fn pty_control_churn_bytes_total(&self) -> usize {
729 self.control_churn_bytes_total.load(Ordering::Acquire)
730 }
731}
732
733#[derive(Debug, Clone, Copy)]
738pub struct InteractivePtyOptions {
739 pub echo_output: bool,
741 pub relay_terminal_input: bool,
743 pub respond_to_queries: bool,
745}
746
747impl Default for InteractivePtyOptions {
748 fn default() -> Self {
749 Self {
750 echo_output: true,
751 relay_terminal_input: true,
752 respond_to_queries: true,
753 }
754 }
755}
756
757#[derive(Debug, Default)]
759pub struct InteractivePtyPumpResult {
760 pub chunks: Vec<Vec<u8>>,
762 pub stream_closed: bool,
764}
765
766pub struct InteractivePtySession {
771 process: NativePtyProcess,
772 options: InteractivePtyOptions,
773}
774
775impl InteractivePtySession {
776 pub fn new(process: NativePtyProcess) -> Self {
778 Self::with_options(process, InteractivePtyOptions::default())
779 }
780
781 pub fn with_options(process: NativePtyProcess, options: InteractivePtyOptions) -> Self {
783 Self { process, options }
784 }
785
786 pub fn process(&self) -> &NativePtyProcess {
788 &self.process
789 }
790
791 pub fn start(&self) -> Result<(), PtyError> {
793 self.process.set_echo(self.options.echo_output);
794 self.process.start_impl()?;
795 if self.options.relay_terminal_input {
796 self.process.start_terminal_input_relay_impl()?;
797 }
798 Ok(())
799 }
800
801 pub fn pump_output(
806 &self,
807 timeout: Option<f64>,
808 consume_all: bool,
809 ) -> Result<InteractivePtyPumpResult, PtyError> {
810 let mut pumped = InteractivePtyPumpResult::default();
811 let mut next_timeout = timeout;
812 loop {
813 match self.process.read_chunk_impl(next_timeout) {
814 Ok(Some(chunk)) => {
815 if self.options.respond_to_queries {
816 self.process.respond_to_queries_impl(&chunk)?;
817 }
818 pumped.chunks.push(chunk);
819 if !consume_all {
820 break;
821 }
822 next_timeout = Some(0.0);
823 }
824 Ok(None) => break,
825 Err(PtyError::Other(message)) if message == "Pseudo-terminal stream is closed" => {
826 pumped.stream_closed = true;
827 break;
828 }
829 Err(err) => return Err(err),
830 }
831 }
832 Ok(pumped)
833 }
834
835 pub fn resize(&self, rows: u16, cols: u16) -> Result<(), PtyError> {
837 self.process.resize_impl(rows, cols)
838 }
839
840 pub fn send_interrupt(&self) -> Result<(), PtyError> {
842 self.process.send_interrupt_impl()
843 }
844
845 pub fn wait(&self, timeout: Option<f64>) -> Result<i32, PtyError> {
847 self.process.wait_impl(timeout)
848 }
849
850 pub fn wait_and_drain(
852 &self,
853 timeout: Option<f64>,
854 drain_timeout: f64,
855 ) -> Result<i32, PtyError> {
856 self.process.wait_and_drain_impl(timeout, drain_timeout)
857 }
858
859 pub fn terminate(&self) -> Result<(), PtyError> {
861 self.process.terminate_impl()
862 }
863
864 pub fn kill(&self) -> Result<(), PtyError> {
866 self.process.kill_impl()
867 }
868
869 pub fn close(&self) -> Result<(), PtyError> {
871 self.process.close_impl()
872 }
873}
874
875impl Drop for NativePtyProcess {
876 fn drop(&mut self) {
877 self.close_nonblocking();
878 }
879}
880
881#[cfg(test)]
882#[path = "../tests/native_pty_process_coverage.rs"]
883mod coverage_tests;