1#![deny(missing_docs)]
5#![doc(html_root_url = "https://docs.rs/tokio-pty-process/0.4.0")]
6
7use async_trait::async_trait;
36
37use futures::Future;
38use io::{Read, Write};
39use libc::{c_int, c_ushort};
40use mio::event::Evented;
41use mio::unix::{EventedFd, UnixReady};
42use mio::{PollOpt, Ready, Token};
43use std::ffi::{CStr, OsStr, OsString};
44use std::fmt;
45use std::fs::{File, OpenOptions};
46use std::io::{self};
47use std::mem;
48use std::os::unix::prelude::*;
49use std::os::unix::process::CommandExt as StdUnixCommandExt;
50use std::{
51 pin::Pin,
52 process::{self, ExitStatus},
53 task::{Context, Poll},
54};
55use tokio::io::PollEvented;
56use tokio::io::{AsyncRead, AsyncWrite};
57use tokio::signal::unix::{signal, Signal, SignalKind};
58mod split;
59pub use split::{AsyncPtyMasterReadHalf, AsyncPtyMasterWriteHalf};
60
61#[derive(Debug)]
67struct AsyncPtyFile(File);
68
69impl AsyncPtyFile {
70 pub fn new(inner: File) -> Self {
71 AsyncPtyFile(inner)
72 }
73}
74
75impl Read for AsyncPtyFile {
76 fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
77 self.0.read(bytes)
78 }
79}
80
81impl Write for AsyncPtyFile {
82 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
83 self.0.write(bytes)
84 }
85
86 fn flush(&mut self) -> io::Result<()> {
87 self.0.flush()
88 }
89}
90
91impl Evented for AsyncPtyFile {
92 fn register(
93 &self,
94 poll: &mio::Poll,
95 token: Token,
96 interest: Ready,
97 opts: PollOpt,
98 ) -> io::Result<()> {
99 EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | UnixReady::hup(), opts)
100 }
101
102 fn reregister(
103 &self,
104 poll: &mio::Poll,
105 token: Token,
106 interest: Ready,
107 opts: PollOpt,
108 ) -> io::Result<()> {
109 EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest | UnixReady::hup(), opts)
110 }
111
112 fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
113 EventedFd(&self.0.as_raw_fd()).deregister(poll)
114 }
115}
116
117pub struct AsyncPtyMaster(PollEvented<AsyncPtyFile>);
122
123impl AsyncPtyMaster {
124 pub fn open() -> Result<Self, io::Error> {
130 let inner = unsafe {
131 const APPLY_NONBLOCK_AFTER_OPEN: bool = cfg!(target_os = "freebsd");
140
141 let fd = if APPLY_NONBLOCK_AFTER_OPEN {
142 libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY)
143 } else {
144 libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY | libc::O_NONBLOCK)
145 };
146
147 if fd < 0 {
148 return Err(io::Error::last_os_error());
149 }
150
151 if libc::grantpt(fd) != 0 {
152 return Err(io::Error::last_os_error());
153 }
154
155 if libc::unlockpt(fd) != 0 {
156 return Err(io::Error::last_os_error());
157 }
158
159 if APPLY_NONBLOCK_AFTER_OPEN {
160 let flags = libc::fcntl(fd, libc::F_GETFL, 0);
161 if flags < 0 {
162 return Err(io::Error::last_os_error());
163 }
164
165 if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) == -1 {
166 return Err(io::Error::last_os_error());
167 }
168 }
169
170 File::from_raw_fd(fd)
171 };
172
173 let evented = PollEvented::new(AsyncPtyFile::new(inner))?;
174 Ok(AsyncPtyMaster(evented))
175 }
176
177 pub fn split(self) -> (AsyncPtyMasterReadHalf, AsyncPtyMasterWriteHalf) {
181 split::split(self)
182 }
183
184 fn open_sync_pty_slave(&self) -> Result<File, io::Error> {
188 let mut buf: [libc::c_char; 512] = [0; 512];
189 let fd = self.as_raw_fd();
190
191 #[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
192 {
193 if unsafe { libc::ptsname_r(fd, buf.as_mut_ptr(), buf.len()) } != 0 {
194 return Err(io::Error::last_os_error());
195 }
196 }
197 #[cfg(any(target_os = "macos", target_os = "freebsd"))]
198 unsafe {
199 let st = libc::ptsname(fd);
200 if st.is_null() {
201 return Err(io::Error::last_os_error());
202 }
203 libc::strncpy(buf.as_mut_ptr(), st, buf.len());
204 }
205
206 let ptsname = OsStr::from_bytes(unsafe { CStr::from_ptr(&buf as _) }.to_bytes());
207 OpenOptions::new().read(true).write(true).open(ptsname)
208 }
209}
210
211impl AsRawFd for AsyncPtyMaster {
212 fn as_raw_fd(&self) -> RawFd {
213 self.0.get_ref().0.as_raw_fd()
214 }
215}
216
217impl AsyncRead for AsyncPtyMaster {
218 fn poll_read(
219 mut self: std::pin::Pin<&mut Self>,
220 cx: &mut std::task::Context<'_>,
221 buf: &mut [u8],
222 ) -> std::task::Poll<io::Result<usize>> {
223 AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
224 }
225}
226
227impl AsyncWrite for AsyncPtyMaster {
228 fn poll_write(
229 mut self: Pin<&mut Self>,
230 cx: &mut std::task::Context<'_>,
231 buf: &[u8],
232 ) -> std::task::Poll<Result<usize, io::Error>> {
233 AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
234 }
235
236 fn poll_flush(
237 mut self: Pin<&mut Self>,
238 cx: &mut std::task::Context<'_>,
239 ) -> std::task::Poll<Result<(), io::Error>> {
240 AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
241 }
242
243 fn poll_shutdown(
244 mut self: Pin<&mut Self>,
245 cx: &mut std::task::Context<'_>,
246 ) -> std::task::Poll<Result<(), io::Error>> {
247 AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
248 }
249}
250
251#[must_use = "futures do nothing unless polled"]
255pub struct Child {
256 inner: process::Child,
257 kill_on_drop: bool,
258 reaped: bool,
259 sigchld: Signal,
260}
261
262impl fmt::Debug for Child {
263 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
264 fmt.debug_struct("Child")
265 .field("pid", &self.inner.id())
266 .field("inner", &self.inner)
267 .field("kill_on_drop", &self.kill_on_drop)
268 .field("reaped", &self.reaped)
269 .field("sigchld", &"..")
270 .finish()
271 }
272}
273
274impl Child {
275 fn new(inner: process::Child) -> Child {
276 Child {
277 inner: inner,
278 kill_on_drop: true,
279 reaped: false,
280 sigchld: signal(SignalKind::child()).expect("could not get sigchld signal"),
281 }
282 }
283
284 pub fn id(&self) -> u32 {
286 self.inner.id()
287 }
288
289 pub fn kill(&mut self) -> io::Result<()> {
293 if self.reaped {
294 Ok(())
295 } else {
296 self.inner.kill()
297 }
298 }
299
300 pub fn forget(mut self) {
306 self.kill_on_drop = false;
307 }
308
309 pub fn poll_exit(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<ExitStatus>> {
311 assert!(!self.reaped);
312
313 loop {
314 match self.try_wait() {
315 Ok(Some(status)) => {
316 self.reaped = true;
317 return Poll::Ready(Ok(status));
318 }
319 Err(e) => return Poll::Ready(Err(e)),
320 _ => {}
321 }
322
323 if self.sigchld.poll_recv(cx).is_pending() {
330 return Poll::Pending;
331 }
332 }
333 }
334
335 fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
336 let id = self.id() as c_int;
337 let mut status = 0;
338
339 loop {
340 match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } {
341 0 => return Ok(None),
342
343 n if n < 0 => {
344 let err = io::Error::last_os_error();
345 if err.kind() == io::ErrorKind::Interrupted {
346 continue;
347 }
348 return Err(err);
349 }
350
351 n => {
352 assert_eq!(n, id);
353 return Ok(Some(ExitStatus::from_raw(status)));
354 }
355 }
356 }
357 }
358}
359
360impl Future for Child {
361 type Output = std::io::Result<ExitStatus>;
362
363 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
364 self.poll_exit(cx)
365 }
366}
367
368impl Drop for Child {
369 fn drop(&mut self) {
370 if self.kill_on_drop {
371 drop(self.kill());
372 }
373 }
374}
375
376pub struct AsyncPtyFd<T: AsAsyncPtyFd>(T);
378
379impl<T: AsAsyncPtyFd> AsyncPtyFd<T> {
380 pub fn from(inner: T) -> Self {
382 AsyncPtyFd(inner)
383 }
384}
385
386impl<T: AsAsyncPtyFd> Future for AsyncPtyFd<T> {
387 type Output = RawFd;
388
389 fn poll(
390 self: Pin<&mut Self>,
391 cx: &mut std::task::Context<'_>,
392 ) -> std::task::Poll<Self::Output> {
393 self.0.as_async_pty_fd(cx)
394 }
395}
396
397pub trait AsAsyncPtyFd {
399 fn as_async_pty_fd(&self, cx: &mut Context<'_>) -> Poll<RawFd>;
401}
402
403impl AsAsyncPtyFd for AsyncPtyMaster {
404 fn as_async_pty_fd(&self, _cx: &mut Context<'_>) -> Poll<RawFd> {
405 Poll::Ready(self.as_raw_fd())
406 }
407}
408
409#[async_trait]
411pub trait PtyMaster: PollPtyMaster {
412 async fn resize(&self, dimensions: (u16, u16)) -> Result<(), io::Error>;
414
415 async fn size(&self) -> Result<(u16, u16), io::Error>;
417}
418
419#[async_trait]
420impl<T: Send + Sync> PtyMaster for T
421where
422 T: PollPtyMaster,
423{
424 async fn resize(&self, dimensions: (u16, u16)) -> Result<(), io::Error> {
425 let resize = Resize {
426 pty: self,
427 cols: dimensions.0,
428 rows: dimensions.1,
429 };
430
431 resize.await
432 }
433
434 async fn size(&self) -> Result<(u16, u16), io::Error> {
435 GetSize(self).await
436 }
437}
438pub trait PollPtyMaster {
440 fn poll_ptsname(&self, cx: &mut Context<'_>) -> Poll<Result<OsString, io::Error>>;
442
443 fn poll_resize(
445 &self,
446 cx: &mut Context<'_>,
447 rows: c_ushort,
448 cols: c_ushort,
449 ) -> Poll<Result<(), io::Error>>;
450
451 fn poll_winsize(&self, cx: &mut Context<'_>) -> Poll<Result<(c_ushort, c_ushort), io::Error>>;
453}
454
455impl<T: AsAsyncPtyFd> PollPtyMaster for T {
456 fn poll_ptsname(&self, cx: &mut Context<'_>) -> Poll<Result<OsString, io::Error>> {
457 let mut buf: [libc::c_char; 512] = [0; 512];
458 let fd = futures::ready!(self.as_async_pty_fd(cx));
459
460 #[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
461 {
462 if unsafe { libc::ptsname_r(fd, buf.as_mut_ptr(), buf.len()) } != 0 {
463 return Poll::Ready(Err(io::Error::last_os_error()));
464 }
465 }
466 #[cfg(any(target_os = "macos", target_os = "freebsd"))]
467 unsafe {
468 let st = libc::ptsname(fd);
469 if st.is_null() {
470 return Poll::Ready(Err(io::Error::last_os_error()));
471 }
472 libc::strncpy(buf.as_mut_ptr(), st, buf.len());
473 }
474 let ptsname = OsStr::from_bytes(unsafe { CStr::from_ptr(&buf as _) }.to_bytes());
475 Poll::Ready(Ok(ptsname.to_os_string()))
476 }
477
478 fn poll_winsize(&self, cx: &mut Context<'_>) -> Poll<Result<(c_ushort, c_ushort), io::Error>> {
479 let fd = futures::ready!(self.as_async_pty_fd(cx));
480 let mut winsz: libc::winsize = unsafe { std::mem::zeroed() };
481 if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ.into(), &mut winsz) } != 0 {
482 return Poll::Ready(Err(io::Error::last_os_error()));
483 }
484 Poll::Ready(Ok((winsz.ws_col, winsz.ws_row)))
485 }
486
487 fn poll_resize(
488 &self,
489 cx: &mut Context<'_>,
490 rows: c_ushort,
491 cols: c_ushort,
492 ) -> Poll<Result<(), io::Error>> {
493 let fd = futures::ready!(self.as_async_pty_fd(cx));
494
495 let winsz = libc::winsize {
496 ws_row: rows,
497 ws_col: cols,
498 ws_xpixel: 0,
499 ws_ypixel: 0,
500 };
501 if unsafe { libc::ioctl(fd, libc::TIOCSWINSZ.into(), &winsz) } != 0 {
502 return Poll::Ready(Err(io::Error::last_os_error()));
503 }
504 Poll::Ready(Ok(()))
505 }
506}
507
508trait CommandExtInternal {
510 fn spawn_pty_async_full(&mut self, ptymaster: &AsyncPtyMaster, raw: bool) -> io::Result<Child>;
511}
512
513impl CommandExtInternal for process::Command {
514 fn spawn_pty_async_full(&mut self, ptymaster: &AsyncPtyMaster, raw: bool) -> io::Result<Child> {
515 let master_fd = ptymaster.as_raw_fd();
516 let slave = ptymaster.open_sync_pty_slave()?;
517 let slave_fd = slave.as_raw_fd();
518
519 self.stdin(slave.try_clone()?);
520 self.stdout(slave.try_clone()?);
521 self.stderr(slave);
522
523 unsafe {
527 self.pre_exec(move || {
528 if raw {
529 let mut attrs: libc::termios = mem::zeroed();
530
531 if libc::tcgetattr(slave_fd, &mut attrs as _) != 0 {
532 return Err(io::Error::last_os_error());
533 }
534
535 libc::cfmakeraw(&mut attrs as _);
536
537 if libc::tcsetattr(slave_fd, libc::TCSANOW, &attrs as _) != 0 {
538 return Err(io::Error::last_os_error());
539 }
540 }
541
542 if libc::close(master_fd) != 0 {
545 return Err(io::Error::last_os_error());
546 }
547
548 if libc::setsid() < 0 {
549 return Err(io::Error::last_os_error());
550 }
551
552 if libc::ioctl(0, libc::TIOCSCTTY.into(), 1) != 0 {
553 return Err(io::Error::last_os_error());
554 }
555 Ok(())
556 });
557 }
558
559 Ok(Child::new(self.spawn()?))
560 }
561}
562
563pub trait CommandExt {
569 fn spawn_pty_async(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child>;
580
581 fn spawn_pty_async_raw(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child>;
592}
593
594impl CommandExt for process::Command {
595 fn spawn_pty_async(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child> {
596 self.spawn_pty_async_full(ptymaster, false)
597 }
598
599 fn spawn_pty_async_raw(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child> {
600 self.spawn_pty_async_full(ptymaster, true)
601 }
602}
603
604struct GetSize<'a, T: PtyMaster + Send>(&'a T);
605impl<'a, T: PtyMaster + Send> Future for GetSize<'a, T> {
606 type Output = io::Result<(c_ushort, c_ushort)>;
607 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
608 self.0.poll_winsize(cx)
609 }
610}
611
612struct Resize<'a, T: PtyMaster + Send> {
613 pub pty: &'a T,
614 pub rows: c_ushort,
615 pub cols: c_ushort,
616}
617
618impl<'a, T: PtyMaster + Send> Future for Resize<'a, T> {
619 type Output = io::Result<()>;
620
621 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
622 self.pty.poll_resize(cx, self.rows, self.cols)
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 extern crate errno;
629 extern crate libc;
630
631 use super::*;
632 use futures::executor::block_on;
633
634 #[tokio::test]
647 async fn basic_nonblocking() {
648 let master = AsyncPtyMaster::open().unwrap();
649
650 let fd = master.as_raw_fd();
651 let mut buf = [0u8; 128];
652 let rval = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 128) };
653 let errno: i32 = errno::errno().into();
654
655 assert_eq!(rval, -1);
656 assert_eq!(errno, libc::EWOULDBLOCK as i32);
657 }
658
659 #[tokio::test]
660 async fn test_winsize() {
661 let master = AsyncPtyMaster::open().expect("Could not open the PTY");
662
663 #[cfg(target_os = "macos")]
666 let mut child = std::process::Command::new("cat")
667 .spawn_pty_async(&master)
668 .expect("Could not spawn child");
669
670 block_on(Resize {
672 pty: &master,
673 cols: 80,
674 rows: 50,
675 })
676 .expect("Could not resize the PTY");
677
678 let (cols, rows) = block_on(GetSize(&master)).expect("Could not get PTY size");
679
680 assert_eq!(cols, 80);
681 assert_eq!(rows, 50);
682
683 #[cfg(target_os = "macos")]
684 child.kill().expect("Could not kill child");
685 }
686
687 #[tokio::test]
688 async fn test_size() {
689 let master = AsyncPtyMaster::open().expect("Could not open the PTY");
690
691 #[cfg(target_os = "macos")]
694 let mut child = std::process::Command::new("cat")
695 .spawn_pty_async(&master)
696 .expect("Could not spawn child");
697
698 let (_rows, _cols) = master.size().await.expect("Could not get PTY size");
699
700 #[cfg(target_os = "macos")]
701 child.kill().expect("Could not kill child");
702 }
703
704 #[tokio::test]
705 async fn test_resize() {
706 let master = AsyncPtyMaster::open().expect("Could not open the PTY");
707
708 #[cfg(target_os = "macos")]
711 let mut child = std::process::Command::new("cat")
712 .spawn_pty_async(&master)
713 .expect("Could not spawn child");
714
715 let _resize = master.resize((80, 50)).await.expect("resize failed");
716
717 #[cfg(target_os = "macos")]
718 child.kill().expect("Could not kill child");
719 }
720
721 #[tokio::test]
722 async fn test_from_fd() {
723 let master = AsyncPtyMaster::open().expect("Could not open the PTY");
724
725 let _fd = AsyncPtyFd::from(master).await;
726 }
727}