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.control_token()) {
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(
383 spawn_context,
384 PtyChild::control_token(&child),
385 self.nice,
386 )
387 .map_err(PtyError::Io)?;
388 let shared = Arc::clone(&self.reader);
389 let echo = Arc::clone(&self.echo);
390 let idle_detector = Arc::clone(&self.idle_detector);
391 let output_bytes = Arc::clone(&self.output_bytes_total);
392 let churn_bytes = Arc::clone(&self.control_churn_bytes_total);
393 let reader_worker = thread::spawn(move || {
394 spawn_pty_reader(
395 reader,
396 shared,
397 echo,
398 idle_detector,
399 output_bytes,
400 churn_bytes,
401 );
402 });
403 *self
404 .reader_worker
405 .lock()
406 .expect("pty reader worker mutex poisoned") = Some(reader_worker);
407
408 *guard = Some(NativePtyHandles {
409 master: Box::new(master) as Box<dyn PtyMaster>,
410 writer: Arc::new(Mutex::new(writer)),
413 child: Box::new(child) as Box<dyn PtyChild>,
414 process_guard,
415 });
416 Ok(())
417 }
418
419 pub fn respond_to_queries_impl(&self, data: &[u8]) -> Result<(), PtyError> {
421 let responses = pty_platform::query_responses(data);
422 if responses.is_empty() {
423 return Ok(());
424 }
425 let writer = {
426 let guard = self.handles.lock().expect("pty handles mutex poisoned");
427 let handles = guard.as_ref().ok_or(PtyError::NotRunning)?;
428 Arc::clone(&handles.writer)
429 };
430 let mut writer = writer.lock().expect("pty writer mutex poisoned");
431 for response in responses {
432 writer.write_all(&response).map_err(PtyError::Io)?;
433 }
434 writer.flush().map_err(PtyError::Io)
435 }
436
437 pub fn resize_impl(&self, rows: u16, cols: u16) -> Result<(), PtyError> {
439 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::resize");
440 let guard = self.handles.lock().expect("pty handles mutex poisoned");
441 if let Some(handles) = guard.as_ref() {
442 pty_platform::resize_pty(
443 handles.master.as_ref(),
444 PtySize {
445 rows,
446 cols,
447 pixel_width: 0,
448 pixel_height: 0,
449 },
450 )
451 .map_err(|error| PtyError::Other(error.to_string()))?;
452 }
453 Ok(())
454 }
455
456 pub fn send_interrupt_impl(&self) -> Result<(), PtyError> {
458 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::send_interrupt");
459 let (target, writer) = {
460 let guard = self.handles.lock().expect("pty handles mutex poisoned");
461 let handles = guard.as_ref().ok_or(PtyError::NotRunning)?;
462 (handles.master.control_token(), Arc::clone(&handles.writer))
463 };
464 let wrote_input =
465 pty_platform::send_pty_interrupt(target, &writer).map_err(PtyError::Io)?;
466 if wrote_input {
467 self.record_input_metrics(&[0x03], false);
468 }
469 Ok(())
470 }
471
472 pub fn wait_impl(&self, timeout: Option<f64>) -> Result<i32, PtyError> {
476 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::wait");
477 if let Some(code) = *self
479 .returncode
480 .lock()
481 .expect("pty returncode mutex poisoned")
482 {
483 return Ok(code);
484 }
485 let start = Instant::now();
486 loop {
487 if let Some(code) = poll_pty_process(&self.handles, &self.returncode)? {
488 return Ok(code);
489 }
490 if timeout.is_some_and(|limit| start.elapsed() >= Duration::from_secs_f64(limit)) {
491 return Err(PtyError::Timeout);
492 }
493 thread::sleep(Duration::from_millis(10));
497 }
498 }
499
500 pub fn terminate_impl(&self) -> Result<(), PtyError> {
502 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::terminate");
503 let should_close = {
504 let mut guard = self.handles.lock().expect("pty handles mutex poisoned");
505 let handles = guard.as_mut().ok_or(PtyError::NotRunning)?;
506 let pid = handles.child.pid();
507 if pid == 0 {
508 return Err(PtyError::NotRunning);
509 }
510 pty_platform::terminate_pty_child(pid).map_err(PtyError::Io)?
511 };
512 if should_close {
513 self.close_impl()?;
514 }
515 Ok(())
516 }
517
518 pub fn kill_impl(&self) -> Result<(), PtyError> {
520 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::kill");
521 if self
522 .handles
523 .lock()
524 .expect("pty handles mutex poisoned")
525 .is_none()
526 {
527 return Err(PtyError::NotRunning);
528 }
529 self.close_impl()
530 }
531
532 pub fn terminate_tree_impl(&self) -> Result<(), PtyError> {
534 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::terminate_tree");
535 let Some(pid) = self.pid()? else {
536 return if self
537 .returncode
538 .lock()
539 .expect("pty returncode mutex poisoned")
540 .is_some()
541 {
542 Ok(())
543 } else {
544 Err(PtyError::NotRunning)
545 };
546 };
547 if pty_platform::signal_pty_tree(pid, false).map_err(PtyError::Io)? {
548 self.close_impl()?;
549 }
550 Ok(())
551 }
552
553 pub fn kill_tree_impl(&self) -> Result<(), PtyError> {
555 crate::rp_rust_debug_scope!("running_process::NativePtyProcess::kill_tree");
556 let Some(pid) = self.pid()? else {
557 return if self
558 .returncode
559 .lock()
560 .expect("pty returncode mutex poisoned")
561 .is_some()
562 {
563 Ok(())
564 } else {
565 Err(PtyError::NotRunning)
566 };
567 };
568 if pty_platform::signal_pty_tree(pid, true).map_err(PtyError::Io)? {
569 self.close_impl()?;
570 }
571 Ok(())
572 }
573
574 pub fn pid(&self) -> Result<Option<u32>, PtyError> {
576 let guard = self.handles.lock().expect("pty handles mutex poisoned");
577 if let Some(handles) = guard.as_ref() {
578 return Ok(pty_platform::preferred_pty_pid(
579 handles.master.as_ref(),
580 handles.child.as_ref(),
581 ));
582 }
583 Ok(None)
584 }
585
586 pub fn read_chunk_impl(&self, timeout: Option<f64>) -> Result<Option<Vec<u8>>, PtyError> {
589 let deadline = timeout.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
590 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
591 loop {
592 if let Some(chunk) = guard.chunks.pop_front() {
593 return Ok(Some(chunk));
594 }
595 if guard.closed {
596 return Err(PtyError::Other("Pseudo-terminal stream is closed".into()));
597 }
598 match deadline {
599 Some(deadline) => {
600 let now = Instant::now();
601 if now >= deadline {
602 return Ok(None); }
604 let wait = deadline.saturating_duration_since(now);
605 let result = self
606 .reader
607 .condvar
608 .wait_timeout(guard, wait)
609 .expect("pty read mutex poisoned");
610 guard = result.0;
611 }
612 None => {
613 guard = self
614 .reader
615 .condvar
616 .wait(guard)
617 .expect("pty read mutex poisoned");
618 }
619 }
620 }
621 }
622
623 pub fn wait_for_reader_closed_impl(&self, timeout: Option<f64>) -> bool {
625 let deadline = timeout.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
626 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
627 loop {
628 if guard.closed {
629 return true;
630 }
631 match deadline {
632 Some(deadline) => {
633 let now = Instant::now();
634 if now >= deadline {
635 return false;
636 }
637 let wait = deadline.saturating_duration_since(now);
638 let result = self
639 .reader
640 .condvar
641 .wait_timeout(guard, wait)
642 .expect("pty read mutex poisoned");
643 guard = result.0;
644 }
645 None => {
646 guard = self
647 .reader
648 .condvar
649 .wait(guard)
650 .expect("pty read mutex poisoned");
651 }
652 }
653 }
654 }
655
656 pub fn wait_and_drain_impl(
658 &self,
659 timeout: Option<f64>,
660 drain_timeout: f64,
661 ) -> Result<i32, PtyError> {
662 let code = self.wait_impl(timeout)?;
663 let deadline = Instant::now() + Duration::from_secs_f64(drain_timeout.max(0.0));
664 let mut guard = self.reader.state.lock().expect("pty read mutex poisoned");
665 while !guard.closed {
666 let remaining = deadline.saturating_duration_since(Instant::now());
667 if remaining.is_zero() {
668 break;
669 }
670 let result = self
671 .reader
672 .condvar
673 .wait_timeout(guard, remaining)
674 .expect("pty read mutex poisoned");
675 guard = result.0;
676 }
677 Ok(code)
678 }
679
680 pub fn set_echo(&self, enabled: bool) {
682 self.echo.store(enabled, Ordering::Release);
683 }
684
685 pub fn echo_enabled(&self) -> bool {
687 self.echo.load(Ordering::Acquire)
688 }
689
690 pub fn attach_idle_detector(&self, detector: &Arc<IdleDetectorCore>) {
692 let mut guard = self
693 .idle_detector
694 .lock()
695 .expect("idle detector mutex poisoned");
696 *guard = Some(Arc::clone(detector));
697 }
698
699 pub fn detach_idle_detector(&self) {
701 let mut guard = self
702 .idle_detector
703 .lock()
704 .expect("idle detector mutex poisoned");
705 *guard = None;
706 }
707
708 pub fn pty_input_bytes_total(&self) -> usize {
710 self.input_bytes_total.load(Ordering::Acquire)
711 }
712
713 pub fn pty_newline_events_total(&self) -> usize {
715 self.newline_events_total.load(Ordering::Acquire)
716 }
717
718 pub fn pty_submit_events_total(&self) -> usize {
720 self.submit_events_total.load(Ordering::Acquire)
721 }
722
723 pub fn pty_output_bytes_total(&self) -> usize {
725 self.output_bytes_total.load(Ordering::Acquire)
726 }
727
728 pub fn pty_control_churn_bytes_total(&self) -> usize {
730 self.control_churn_bytes_total.load(Ordering::Acquire)
731 }
732}
733
734#[derive(Debug, Clone, Copy)]
739pub struct InteractivePtyOptions {
740 pub echo_output: bool,
742 pub relay_terminal_input: bool,
744 pub respond_to_queries: bool,
746}
747
748impl Default for InteractivePtyOptions {
749 fn default() -> Self {
750 Self {
751 echo_output: true,
752 relay_terminal_input: true,
753 respond_to_queries: true,
754 }
755 }
756}
757
758#[derive(Debug, Default)]
760pub struct InteractivePtyPumpResult {
761 pub chunks: Vec<Vec<u8>>,
763 pub stream_closed: bool,
765}
766
767pub struct InteractivePtySession {
772 process: NativePtyProcess,
773 options: InteractivePtyOptions,
774}
775
776impl InteractivePtySession {
777 pub fn new(process: NativePtyProcess) -> Self {
779 Self::with_options(process, InteractivePtyOptions::default())
780 }
781
782 pub fn with_options(process: NativePtyProcess, options: InteractivePtyOptions) -> Self {
784 Self { process, options }
785 }
786
787 pub fn process(&self) -> &NativePtyProcess {
789 &self.process
790 }
791
792 pub fn start(&self) -> Result<(), PtyError> {
794 self.process.set_echo(self.options.echo_output);
795 self.process.start_impl()?;
796 if self.options.relay_terminal_input {
797 self.process.start_terminal_input_relay_impl()?;
798 }
799 Ok(())
800 }
801
802 pub fn pump_output(
807 &self,
808 timeout: Option<f64>,
809 consume_all: bool,
810 ) -> Result<InteractivePtyPumpResult, PtyError> {
811 let mut pumped = InteractivePtyPumpResult::default();
812 let mut next_timeout = timeout;
813 loop {
814 match self.process.read_chunk_impl(next_timeout) {
815 Ok(Some(chunk)) => {
816 if self.options.respond_to_queries {
817 self.process.respond_to_queries_impl(&chunk)?;
818 }
819 pumped.chunks.push(chunk);
820 if !consume_all {
821 break;
822 }
823 next_timeout = Some(0.0);
824 }
825 Ok(None) => break,
826 Err(PtyError::Other(message)) if message == "Pseudo-terminal stream is closed" => {
827 pumped.stream_closed = true;
828 break;
829 }
830 Err(err) => return Err(err),
831 }
832 }
833 Ok(pumped)
834 }
835
836 pub fn resize(&self, rows: u16, cols: u16) -> Result<(), PtyError> {
838 self.process.resize_impl(rows, cols)
839 }
840
841 pub fn send_interrupt(&self) -> Result<(), PtyError> {
843 self.process.send_interrupt_impl()
844 }
845
846 pub fn wait(&self, timeout: Option<f64>) -> Result<i32, PtyError> {
848 self.process.wait_impl(timeout)
849 }
850
851 pub fn wait_and_drain(
853 &self,
854 timeout: Option<f64>,
855 drain_timeout: f64,
856 ) -> Result<i32, PtyError> {
857 self.process.wait_and_drain_impl(timeout, drain_timeout)
858 }
859
860 pub fn terminate(&self) -> Result<(), PtyError> {
862 self.process.terminate_impl()
863 }
864
865 pub fn kill(&self) -> Result<(), PtyError> {
867 self.process.kill_impl()
868 }
869
870 pub fn close(&self) -> Result<(), PtyError> {
872 self.process.close_impl()
873 }
874}
875
876impl Drop for NativePtyProcess {
877 fn drop(&mut self) {
878 self.close_nonblocking();
879 }
880}
881
882#[cfg(test)]
883#[path = "../tests/native_pty_process_coverage.rs"]
884mod coverage_tests;