From f8780ca1e76286688b74d8a6c64d5fadf3cfd2a1 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Tue, 28 Dec 2021 03:33:52 -0500 Subject: wip --- src/async_std.rs | 7 +- src/command.rs | 172 +++++++++++++++++++++---------------------- src/command/async_process.rs | 33 ++++----- src/command/std.rs | 33 ++++----- src/command/tokio.rs | 34 ++++----- src/lib.rs | 2 +- src/pty.rs | 83 +++++++++++++++++---- src/pty/async_io.rs | 49 +----------- src/pty/std.rs | 44 +---------- src/pty/tokio.rs | 72 +++--------------- src/smol.rs | 7 +- src/std.rs | 6 +- src/tokio.rs | 6 +- 13 files changed, 235 insertions(+), 313 deletions(-) (limited to 'src') diff --git a/src/async_std.rs b/src/async_std.rs index 2a66647..8d0e877 100644 --- a/src/async_std.rs +++ b/src/async_std.rs @@ -1,2 +1,5 @@ -pub type Child = - crate::Child; +type Pt = async_io::Async; + +pub type Command = crate::Command; +pub type Child = crate::Child; +pub type Pty = crate::Pty; diff --git a/src/command.rs b/src/command.rs index 303b885..b3d1104 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,6 +1,4 @@ -use crate::pty::Pty as _; - -use ::std::os::unix::io::AsRawFd as _; +use ::std::os::unix::io::{AsRawFd as _, FromRawFd as _}; #[cfg(any(feature = "backend-async-std", feature = "backend-smol"))] mod async_process; @@ -9,47 +7,59 @@ mod std; #[cfg(feature = "backend-tokio")] mod tokio; -/// Adds methods to the existing `Command` struct. -/// -/// This trait is automatically implemented for a backend's `Command` struct -/// when that backend's feature is enabled. -pub trait Command { - type Child; - type Pty; +pub struct Command { + inner: C, + stdin_set: bool, + stdout_set: bool, + stderr_set: bool, - /// Creates a new pty, associates the command's stdin/stdout/stderr with - /// that pty, and then calls `spawn`. This will override any previous - /// calls to `stdin`/`stdout`/`stderr`. - /// - /// # Errors - /// * `Error::CreatePty`: error creating pty - /// * `Error::SetTermSize`: error setting terminal size - /// * `Error::Spawn`: error spawning subprocess - fn spawn_pty( - &mut self, - size: Option<&crate::pty::Size>, - ) -> crate::error::Result>; + _phantom: ::std::marker::PhantomData

, } -impl Command for T -where - T: Impl, - T::Pty: crate::pty::Pty, - <::Pty as crate::pty::Pty>::Pt: ::std::os::unix::io::AsRawFd, -{ - type Child = T::Child; - type Pty = T::Pty; +impl Command { + pub fn new>(program: S) -> Self { + Self { + inner: C::new_impl(program.as_ref()), + stdin_set: false, + stdout_set: false, + stderr_set: false, + + _phantom: ::std::marker::PhantomData, + } + } + + pub fn stdin>(&mut self, cfg: T) { + self.stdin_set = true; + self.inner.stdin_impl(cfg.into()); + } + + pub fn stdout>(&mut self, cfg: T) { + self.stdout_set = true; + self.inner.stdout_impl(cfg.into()); + } + + pub fn stderr>(&mut self, cfg: T) { + self.stderr_set = true; + self.inner.stderr_impl(cfg.into()); + } - fn spawn_pty( + pub fn spawn( &mut self, - size: Option<&crate::pty::Size>, - ) -> crate::error::Result> { - let (pty, pts, stdin, stdout, stderr) = setup_pty::(size)?; + pty: crate::Pty

, + ) -> crate::Result::Child, P>> { + let (pts, stdin, stdout, stderr) = pty.setup()?; let pt_fd = pty.pt().as_raw_fd(); let pts_fd = pts.as_raw_fd(); - self.std_fds(stdin, stdout, stderr); + self.inner + .stdin_impl(unsafe { ::std::process::Stdio::from_raw_fd(stdin) }); + self.inner.stdout_impl(unsafe { + ::std::process::Stdio::from_raw_fd(stdout) + }); + self.inner.stderr_impl(unsafe { + ::std::process::Stdio::from_raw_fd(stderr) + }); let pre_exec = move || { nix::unistd::setsid()?; @@ -75,30 +85,44 @@ where // safe because setsid() and close() are async-signal-safe functions // and ioctl() is a raw syscall (which is inherently // async-signal-safe). - unsafe { self.pre_exec_impl(pre_exec) }; + unsafe { self.inner.pre_exec_impl(pre_exec) }; + + let child = self.inner.spawn_impl()?; + + Ok(Child::new(child, pty)) + } +} + +impl ::std::ops::Deref for Command { + type Target = C; - let child = self.spawn_impl().map_err(crate::error::spawn)?; + fn deref(&self) -> &Self::Target { + &self.inner + } +} - Ok(Child { child, pty }) +impl ::std::ops::DerefMut for Command { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } } -/// Wrapper struct adding pty methods to the normal `Child` struct. -pub struct Child { - child: C, - pty: P, +pub struct Child { + inner: C, + pty: crate::Pty

, } -impl Child -where - P: crate::pty::Pty, -{ +impl Child { + fn new(inner: C, pty: crate::Pty

) -> Self { + Self { inner, pty } + } + /// Returns a reference to the pty. /// /// The underlying pty instance is guaranteed to implement /// [`AsRawFd`](::std::os::unix::io::AsRawFd), as well as the appropriate /// `Read` and `Write` traits for the associated backend. - pub fn pty(&self) -> &P::Pt { + pub fn pty(&self) -> &P { self.pty.pt() } @@ -111,7 +135,7 @@ where /// This method is primarily useful for the tokio backend, since tokio's /// `AsyncRead` and `AsyncWrite` traits have methods which take mutable /// references. - pub fn pty_mut(&mut self) -> &mut P::Pt { + pub fn pty_mut(&mut self) -> &mut P { self.pty.pt_mut() } @@ -124,70 +148,38 @@ where /// * `Error::SetTermSize`: error setting terminal size pub fn resize_pty( &self, - size: &crate::pty::Size, + size: crate::pty::Size, ) -> crate::error::Result<()> { self.pty.resize(size) } } -impl ::std::ops::Deref for Child { +impl ::std::ops::Deref for Child { type Target = C; fn deref(&self) -> &Self::Target { - &self.child + &self.inner } } -impl ::std::ops::DerefMut for Child { +impl ::std::ops::DerefMut for Child { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.child + &mut self.inner } } -// XXX shouldn't be pub? pub trait Impl { type Child; - type Pty; - fn std_fds( - &mut self, - stdin: ::std::os::unix::io::RawFd, - stdout: ::std::os::unix::io::RawFd, - stderr: ::std::os::unix::io::RawFd, - ); + fn new_impl(program: &::std::ffi::OsStr) -> Self; + fn stdin_impl(&mut self, cfg: ::std::process::Stdio); + fn stdout_impl(&mut self, cfg: ::std::process::Stdio); + fn stderr_impl(&mut self, cfg: ::std::process::Stdio); unsafe fn pre_exec_impl(&mut self, f: F) where F: FnMut() -> ::std::io::Result<()> + Send + Sync + 'static; - fn spawn_impl(&mut self) -> ::std::io::Result; -} - -fn setup_pty

( - size: Option<&crate::pty::Size>, -) -> crate::error::Result<( - P, - ::std::fs::File, - ::std::os::unix::io::RawFd, - ::std::os::unix::io::RawFd, - ::std::os::unix::io::RawFd, -)> -where - P: crate::pty::Pty, -{ - let pty = P::new()?; - if let Some(size) = size { - pty.resize(size)?; - } - - let pts = pty.pts()?; - let pts_fd = pts.as_raw_fd(); - - let stdin = nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; - let stdout = - nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; - let stderr = - nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; - Ok((pty, pts, stdin, stdout, stderr)) + fn spawn_impl(&mut self) -> crate::Result; } fn set_controlling_terminal( diff --git a/src/command/async_process.rs b/src/command/async_process.rs index 4a1d042..a0aaa47 100644 --- a/src/command/async_process.rs +++ b/src/command/async_process.rs @@ -1,23 +1,22 @@ use async_process::unix::CommandExt as _; -use std::os::unix::io::FromRawFd as _; impl super::Impl for async_process::Command { type Child = async_process::Child; - type Pty = crate::pty::async_io::Pty; - fn std_fds( - &mut self, - stdin: ::std::os::unix::io::RawFd, - stdout: ::std::os::unix::io::RawFd, - stderr: ::std::os::unix::io::RawFd, - ) { - // safe because the fds are valid (otherwise pty.pts() or dup() would - // have returned an Err and we would have exited early) and are not - // owned by any other structure (since dup() returns a fresh copy of - // the file descriptor), allowing from_raw_fd to take ownership of it. - self.stdin(unsafe { std::process::Stdio::from_raw_fd(stdin) }) - .stdout(unsafe { std::process::Stdio::from_raw_fd(stdout) }) - .stderr(unsafe { std::process::Stdio::from_raw_fd(stderr) }); + fn new_impl(program: &::std::ffi::OsStr) -> Self { + Self::new(program) + } + + fn stdin_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdin(cfg); + } + + fn stdout_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdout(cfg); + } + + fn stderr_impl(&mut self, cfg: ::std::process::Stdio) { + self.stderr(cfg); } unsafe fn pre_exec_impl(&mut self, f: F) @@ -27,7 +26,7 @@ impl super::Impl for async_process::Command { self.pre_exec(f); } - fn spawn_impl(&mut self) -> ::std::io::Result { - self.spawn() + fn spawn_impl(&mut self) -> crate::Result { + self.spawn().map_err(crate::error::spawn) } } diff --git a/src/command/std.rs b/src/command/std.rs index 5c08808..2a89db5 100644 --- a/src/command/std.rs +++ b/src/command/std.rs @@ -1,23 +1,22 @@ -use std::os::unix::io::FromRawFd as _; use std::os::unix::process::CommandExt as _; impl super::Impl for std::process::Command { type Child = std::process::Child; - type Pty = crate::pty::std::Pty; - fn std_fds( - &mut self, - stdin: ::std::os::unix::io::RawFd, - stdout: ::std::os::unix::io::RawFd, - stderr: ::std::os::unix::io::RawFd, - ) { - // safe because the fds are valid (otherwise pty.pts() or dup() would - // have returned an Err and we would have exited early) and are not - // owned by any other structure (since dup() returns a fresh copy of - // the file descriptor), allowing from_raw_fd to take ownership of it. - self.stdin(unsafe { std::process::Stdio::from_raw_fd(stdin) }) - .stdout(unsafe { std::process::Stdio::from_raw_fd(stdout) }) - .stderr(unsafe { std::process::Stdio::from_raw_fd(stderr) }); + fn new_impl(program: &::std::ffi::OsStr) -> Self { + Self::new(program) + } + + fn stdin_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdin(cfg); + } + + fn stdout_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdout(cfg); + } + + fn stderr_impl(&mut self, cfg: ::std::process::Stdio) { + self.stderr(cfg); } unsafe fn pre_exec_impl(&mut self, f: F) @@ -27,7 +26,7 @@ impl super::Impl for std::process::Command { self.pre_exec(f); } - fn spawn_impl(&mut self) -> ::std::io::Result { - self.spawn() + fn spawn_impl(&mut self) -> crate::Result { + self.spawn().map_err(crate::error::spawn) } } diff --git a/src/command/tokio.rs b/src/command/tokio.rs index 58d2c26..c63b18a 100644 --- a/src/command/tokio.rs +++ b/src/command/tokio.rs @@ -1,22 +1,20 @@ -use std::os::unix::io::FromRawFd as _; - impl super::Impl for tokio::process::Command { type Child = tokio::process::Child; - type Pty = crate::pty::tokio::Pty; - fn std_fds( - &mut self, - stdin: ::std::os::unix::io::RawFd, - stdout: ::std::os::unix::io::RawFd, - stderr: ::std::os::unix::io::RawFd, - ) { - // safe because the fds are valid (otherwise pty.pts() or dup() would - // have returned an Err and we would have exited early) and are not - // owned by any other structure (since dup() returns a fresh copy of - // the file descriptor), allowing from_raw_fd to take ownership of it. - self.stdin(unsafe { std::process::Stdio::from_raw_fd(stdin) }) - .stdout(unsafe { std::process::Stdio::from_raw_fd(stdout) }) - .stderr(unsafe { std::process::Stdio::from_raw_fd(stderr) }); + fn new_impl(program: &::std::ffi::OsStr) -> Self { + Self::new(program) + } + + fn stdin_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdin(cfg); + } + + fn stdout_impl(&mut self, cfg: ::std::process::Stdio) { + self.stdout(cfg); + } + + fn stderr_impl(&mut self, cfg: ::std::process::Stdio) { + self.stderr(cfg); } unsafe fn pre_exec_impl(&mut self, f: F) @@ -26,7 +24,7 @@ impl super::Impl for tokio::process::Command { self.pre_exec(f); } - fn spawn_impl(&mut self) -> ::std::io::Result { - self.spawn() + fn spawn_impl(&mut self) -> crate::Result { + self.spawn().map_err(crate::error::spawn) } } diff --git a/src/lib.rs b/src/lib.rs index 06f1bbd..5ccaed5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ pub use command::{Child, Command}; mod error; pub use error::{Error, Result, Source}; mod pty; -pub use pty::Size; +pub use pty::{Pty, Size}; #[cfg(feature = "backend-async-std")] pub mod async_std; diff --git a/src/pty.rs b/src/pty.rs index a569f88..72c59e1 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -1,4 +1,4 @@ -use ::std::os::unix::io::IntoRawFd as _; +use ::std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _}; #[cfg(any(feature = "backend-async-std", feature = "backend-smol"))] pub mod async_io; @@ -7,19 +7,76 @@ pub mod std; #[cfg(feature = "backend-tokio")] pub mod tokio; -pub trait Pty { - type Pt; +pub struct Pty { + pt: Pt, + ptsname: ::std::path::PathBuf, +} + +impl Pty { + pub fn new() -> crate::error::Result { + let (pt_fd, ptsname) = create_pt()?; + + // safe because posix_openpt (or the previous functions operating on + // the result) would have returned an Err (causing us to return early) + // if the file descriptor was invalid. additionally, into_raw_fd gives + // up ownership over the file descriptor, allowing the newly created + // File object to take full ownership. + let fh = unsafe { ::std::fs::File::from_raw_fd(pt_fd) }; + let pt = Pt::new_from_fh(fh)?; + + Ok(Self { pt, ptsname }) + } + + pub fn pt(&self) -> &Pt { + &self.pt + } + + pub fn pt_mut(&mut self) -> &mut Pt { + &mut self.pt + } + + pub fn resize(&self, size: crate::Size) -> crate::error::Result<()> { + set_term_size(self.pt().as_raw_fd(), size) + .map_err(crate::error::set_term_size) + } + + fn pts(&self) -> crate::error::Result<::std::fs::File> { + let fh = ::std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(&self.ptsname) + .map_err(crate::error::create_pty)?; + Ok(fh) + } + + pub(crate) fn setup( + &self, + ) -> crate::Result<( + ::std::fs::File, + ::std::os::unix::io::RawFd, + ::std::os::unix::io::RawFd, + ::std::os::unix::io::RawFd, + )> { + let pts = self.pts()?; + let pts_fd = pts.as_raw_fd(); + + let stdin = + nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; + let stdout = + nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; + let stderr = + nix::unistd::dup(pts_fd).map_err(crate::error::create_pty)?; + + Ok((pts, stdin, stdout, stderr)) + } +} - fn new() -> crate::error::Result - where - Self: Sized; - fn pt(&self) -> &Self::Pt; - fn pt_mut(&mut self) -> &mut Self::Pt; - fn pts(&self) -> crate::error::Result<::std::fs::File>; - fn resize(&self, size: &super::Size) -> crate::error::Result<()>; +pub trait Impl: ::std::os::unix::io::AsRawFd + Sized { + fn new_from_fh(fh: ::std::fs::File) -> crate::Result; } /// Represents the size of the pty. +#[derive(Debug, Clone, Copy)] pub struct Size { row: u16, col: u16, @@ -58,8 +115,8 @@ impl Size { } } -impl From<&Size> for nix::pty::Winsize { - fn from(size: &Size) -> Self { +impl From for nix::pty::Winsize { + fn from(size: Size) -> Self { Self { ws_row: size.row, ws_col: size.col, @@ -96,7 +153,7 @@ nix::ioctl_write_ptr_bad!( fn set_term_size( fd: ::std::os::unix::io::RawFd, - size: &Size, + size: Size, ) -> nix::Result<()> { let size = size.into(); // safe because std::fs::File is required to contain a valid file diff --git a/src/pty/async_io.rs b/src/pty/async_io.rs index 304f403..d76c92c 100644 --- a/src/pty/async_io.rs +++ b/src/pty/async_io.rs @@ -1,48 +1,5 @@ -use std::os::unix::io::{AsRawFd as _, FromRawFd as _}; - -pub struct Pty { - pt: async_io::Async, - ptsname: std::path::PathBuf, -} - -impl super::Pty for Pty { - type Pt = async_io::Async; - - fn new() -> crate::error::Result { - let (pt_fd, ptsname) = super::create_pt()?; - - // safe because posix_openpt (or the previous functions operating on - // the result) would have returned an Err (causing us to return early) - // if the file descriptor was invalid. additionally, into_raw_fd gives - // up ownership over the file descriptor, allowing the newly created - // File object to take full ownership. - let pt = unsafe { std::fs::File::from_raw_fd(pt_fd) }; - - let pt = - async_io::Async::new(pt).map_err(crate::error::create_pty)?; - - Ok(Self { pt, ptsname }) - } - - fn pt(&self) -> &Self::Pt { - &self.pt - } - - fn pt_mut(&mut self) -> &mut Self::Pt { - &mut self.pt - } - - fn pts(&self) -> crate::error::Result { - let fh = std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(&self.ptsname) - .map_err(crate::error::create_pty)?; - Ok(fh) - } - - fn resize(&self, size: &super::Size) -> crate::error::Result<()> { - super::set_term_size(self.pt().as_raw_fd(), size) - .map_err(crate::error::set_term_size) +impl super::Impl for async_io::Async { + fn new_from_fh(fh: std::fs::File) -> crate::Result { + Self::new(fh).map_err(crate::error::create_pty) } } diff --git a/src/pty/std.rs b/src/pty/std.rs index c907052..f88e7ba 100644 --- a/src/pty/std.rs +++ b/src/pty/std.rs @@ -1,45 +1,5 @@ -use std::os::unix::io::{AsRawFd as _, FromRawFd as _}; - -pub struct Pty { - pt: std::fs::File, - ptsname: std::path::PathBuf, -} - -impl super::Pty for Pty { - type Pt = std::fs::File; - - fn new() -> crate::error::Result { - let (pt_fd, ptsname) = super::create_pt()?; - - // safe because posix_openpt (or the previous functions operating on - // the result) would have returned an Err (causing us to return early) - // if the file descriptor was invalid. additionally, into_raw_fd gives - // up ownership over the file descriptor, allowing the newly created - // File object to take full ownership. - let pt = unsafe { std::fs::File::from_raw_fd(pt_fd) }; - - Ok(Self { pt, ptsname }) - } - - fn pt(&self) -> &Self::Pt { - &self.pt - } - - fn pt_mut(&mut self) -> &mut Self::Pt { - &mut self.pt - } - - fn pts(&self) -> crate::error::Result { - let fh = std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(&self.ptsname) - .map_err(crate::error::create_pty)?; +impl super::Impl for std::fs::File { + fn new_from_fh(fh: std::fs::File) -> crate::Result { Ok(fh) } - - fn resize(&self, size: &super::Size) -> crate::error::Result<()> { - super::set_term_size(self.pt().as_raw_fd(), size) - .map_err(crate::error::set_term_size) - } } diff --git a/src/pty/tokio.rs b/src/pty/tokio.rs index e11bffb..ff0b280 100644 --- a/src/pty/tokio.rs +++ b/src/pty/tokio.rs @@ -1,5 +1,13 @@ use std::io::{Read as _, Write as _}; -use std::os::unix::io::{AsRawFd as _, FromRawFd as _}; + +impl super::Impl for AsyncPty { + fn new_from_fh(fh: std::fs::File) -> crate::Result { + Ok(Self( + tokio::io::unix::AsyncFd::new(fh) + .map_err(crate::error::create_pty)?, + )) + } +} // ideally i would just be able to use tokio::fs::File::from_std on the // std::fs::File i create from the pty fd, but it appears that tokio::fs::File @@ -91,65 +99,3 @@ impl tokio::io::AsyncWrite for AsyncPty { std::task::Poll::Ready(Ok(())) } } - -pub struct Pty { - pt: AsyncPty, - ptsname: std::path::PathBuf, -} - -impl super::Pty for Pty { - type Pt = AsyncPty; - - fn new() -> crate::error::Result { - let (pt_fd, ptsname) = super::create_pt()?; - - let bits = nix::fcntl::fcntl(pt_fd, nix::fcntl::FcntlArg::F_GETFL) - .map_err(crate::error::create_pty)?; - // this should be safe because i am just using the return value of - // F_GETFL directly, but for whatever reason nix doesn't like - // from_bits(bits) (it claims it has an unknown field) - let opts = unsafe { - nix::fcntl::OFlag::from_bits_unchecked( - bits | nix::fcntl::OFlag::O_NONBLOCK.bits(), - ) - }; - nix::fcntl::fcntl(pt_fd, nix::fcntl::FcntlArg::F_SETFL(opts)) - .map_err(crate::error::create_pty)?; - - // safe because posix_openpt (or the previous functions operating on - // the result) would have returned an Err (causing us to return early) - // if the file descriptor was invalid. additionally, into_raw_fd gives - // up ownership over the file descriptor, allowing the newly created - // File object to take full ownership. - let pt = unsafe { std::fs::File::from_raw_fd(pt_fd) }; - - let pt = AsyncPty( - tokio::io::unix::AsyncFd::new(pt) - .map_err(crate::error::create_pty)?, - ); - - Ok(Self { pt, ptsname }) - } - - fn pt(&self) -> &Self::Pt { - &self.pt - } - - fn pt_mut(&mut self) -> &mut Self::Pt { - &mut self.pt - } - - fn pts(&self) -> crate::error::Result { - let fh = std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(&self.ptsname) - .map_err(crate::error::create_pty)?; - Ok(fh) - } - - fn resize(&self, size: &super::Size) -> crate::error::Result<()> { - super::set_term_size(self.pt().as_raw_fd(), size) - .map_err(crate::error::set_term_size) - } -} diff --git a/src/smol.rs b/src/smol.rs index 2a66647..8d0e877 100644 --- a/src/smol.rs +++ b/src/smol.rs @@ -1,2 +1,5 @@ -pub type Child = - crate::Child; +type Pt = async_io::Async; + +pub type Command = crate::Command; +pub type Child = crate::Child; +pub type Pty = crate::Pty; diff --git a/src/std.rs b/src/std.rs index 969c51a..4bc0eb0 100644 --- a/src/std.rs +++ b/src/std.rs @@ -1 +1,5 @@ -pub type Child = crate::Child; +type Pt = std::fs::File; + +pub type Command = crate::Command; +pub type Child = crate::Child; +pub type Pty = crate::Pty; diff --git a/src/tokio.rs b/src/tokio.rs index b0ad53c..e2efa56 100644 --- a/src/tokio.rs +++ b/src/tokio.rs @@ -1 +1,5 @@ -pub type Child = crate::Child; +type Pt = crate::pty::tokio::AsyncPty; + +pub type Command = crate::Command; +pub type Child = crate::Child; +pub type Pty = crate::Pty; -- cgit v1.2.3-54-g00ecf