aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-12-28 03:33:52 -0500
committerJesse Luehrs <doy@tozt.net>2021-12-28 05:28:28 -0500
commitf8780ca1e76286688b74d8a6c64d5fadf3cfd2a1 (patch)
treeb1e0fe6a378f3a8810e0332ca572a86185fd556c
parentb181b63a69d5db78769c1c3723a9940f66491466 (diff)
downloadpty-process-f8780ca1e76286688b74d8a6c64d5fadf3cfd2a1.tar.gz
pty-process-f8780ca1e76286688b74d8a6c64d5fadf3cfd2a1.zip
wip
-rw-r--r--Cargo.toml3
-rw-r--r--examples/async-std.rs26
-rw-r--r--examples/basic.rs28
-rw-r--r--examples/interhack.rs18
-rw-r--r--examples/smol.rs27
-rw-r--r--examples/tokio.rs19
-rw-r--r--src/async_std.rs7
-rw-r--r--src/command.rs172
-rw-r--r--src/command/async_process.rs33
-rw-r--r--src/command/std.rs33
-rw-r--r--src/command/tokio.rs34
-rw-r--r--src/lib.rs2
-rw-r--r--src/pty.rs83
-rw-r--r--src/pty/async_io.rs49
-rw-r--r--src/pty/std.rs44
-rw-r--r--src/pty/tokio.rs72
-rw-r--r--src/smol.rs7
-rw-r--r--src/std.rs6
-rw-r--r--src/tokio.rs6
-rw-r--r--tests/basic.rs28
-rw-r--r--tests/pipe.rs68
-rw-r--r--tests/winch.rs18
22 files changed, 394 insertions, 389 deletions
diff --git a/Cargo.toml b/Cargo.toml
index baf4b8c..aeefa41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,3 +36,6 @@ backend-std = []
backend-async-std = ["async-io", "async-process"]
backend-smol = ["async-io", "async-process"]
backend-tokio = ["tokio", "futures"]
+
+[patch.crates-io]
+async-process = { git = "https://github.com/doy/async-process", branch = "status-drop" }
diff --git a/examples/async-std.rs b/examples/async-std.rs
index 6e1b941..880c91b 100644
--- a/examples/async-std.rs
+++ b/examples/async-std.rs
@@ -7,7 +7,7 @@ mod main {
use async_std::prelude::FutureExt as _;
pub async fn run(
- child: &pty_process::async_std::Child,
+ child: &mut pty_process::async_std::Child,
) -> std::result::Result<(), Box<dyn std::error::Error + '_>> {
let _raw = super::raw_guard::RawGuard::new();
@@ -38,18 +38,18 @@ mod main {
stdout.flush().await.unwrap();
}
Err(e) => {
- // EIO means that the process closed the other
- // end of the pty
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
+ eprintln!("pty read failed: {:?}", e);
break;
}
}
}
});
- ex.run(input.race(output)).await;
+ let wait = async {
+ child.status_no_drop().await.unwrap();
+ };
+
+ ex.run(input.race(output).race(wait)).await;
Ok(())
}
@@ -57,15 +57,15 @@ mod main {
#[cfg(feature = "backend-async-std")]
fn main() {
- use pty_process::Command as _;
use std::os::unix::process::ExitStatusExt as _;
let status = async_std::task::block_on(async {
- let mut child = async_std::process::Command::new("sleep")
- .args(&["500"])
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
- main::run(&child).await.unwrap();
+ let pty = pty_process::async_std::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd = pty_process::async_std::Command::new("tac");
+ // cmd.args(&["500"]);
+ let mut child = cmd.spawn(pty).unwrap();
+ main::run(&mut child).await.unwrap();
child.status().await.unwrap()
});
std::process::exit(
diff --git a/examples/basic.rs b/examples/basic.rs
index bee6758..1e5913b 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -5,7 +5,7 @@ mod main {
use std::io::{Read as _, Write as _};
use std::os::unix::io::AsRawFd as _;
- pub fn run(child: &pty_process::std::Child) {
+ pub fn run(child: &mut pty_process::std::Child) {
let _raw = super::raw_guard::RawGuard::new();
let mut buf = [0_u8; 4096];
let pty = child.pty().as_raw_fd();
@@ -34,11 +34,7 @@ mod main {
stdout.flush().unwrap();
}
Err(e) => {
- // EIO means that the process closed the other
- // end of the pty
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
+ eprintln!("pty read failed: {:?}", e);
break;
}
};
@@ -62,21 +58,29 @@ mod main {
break;
}
}
+ match child.try_wait() {
+ Ok(Some(_)) => break,
+ Ok(None) => {}
+ Err(e) => {
+ println!("wait failed: {:?}", e);
+ break;
+ }
+ }
}
}
}
#[cfg(feature = "backend-std")]
fn main() {
- use pty_process::Command as _;
use std::os::unix::process::ExitStatusExt as _;
- let mut child = std::process::Command::new("sleep")
- .args(&["500"])
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::std::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd = pty_process::std::Command::new("tac");
+ // cmd.args(&["500"]);
+ let mut child = cmd.spawn(pty).unwrap();
- main::run(&child);
+ main::run(&mut child);
let status = child.wait().unwrap();
std::process::exit(
diff --git a/examples/interhack.rs b/examples/interhack.rs
index 8caebb4..e636282 100644
--- a/examples/interhack.rs
+++ b/examples/interhack.rs
@@ -5,7 +5,7 @@ mod main {
use smol::io::{AsyncReadExt as _, AsyncWriteExt as _};
pub async fn run(
- child: &pty_process::smol::Child,
+ child: &mut pty_process::smol::Child,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let _raw = super::raw_guard::RawGuard::new();
@@ -82,7 +82,12 @@ mod main {
}
});
- ex.run(smol::future::or(input, output)).await;
+ let wait = async {
+ child.status_no_drop().await.unwrap();
+ };
+
+ ex.run(smol::future::or(smol::future::or(input, output), wait))
+ .await;
Ok(())
}
@@ -90,7 +95,6 @@ mod main {
#[cfg(feature = "backend-smol")]
fn main() {
- use pty_process::Command as _;
use std::os::unix::process::ExitStatusExt as _;
let (w, h) = if let Some((w, h)) = term_size::dimensions() {
@@ -99,10 +103,12 @@ fn main() {
(80, 24)
};
let status = smol::block_on(async {
- let mut child = smol::process::Command::new("nethack")
- .spawn_pty(Some(&pty_process::Size::new(h, w)))
+ let pty = pty_process::smol::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(h, w)).unwrap();
+ let mut child = pty_process::smol::Command::new("nethack")
+ .spawn(pty)
.unwrap();
- main::run(&child).await.unwrap();
+ main::run(&mut child).await.unwrap();
child.status().await.unwrap()
});
std::process::exit(
diff --git a/examples/smol.rs b/examples/smol.rs
index d519fbe..b0b90e4 100644
--- a/examples/smol.rs
+++ b/examples/smol.rs
@@ -5,7 +5,7 @@ mod main {
use smol::io::{AsyncReadExt as _, AsyncWriteExt as _};
pub async fn run(
- child: &pty_process::smol::Child,
+ child: &mut pty_process::smol::Child,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
let _raw = super::raw_guard::RawGuard::new();
@@ -36,18 +36,19 @@ mod main {
stdout.flush().await.unwrap();
}
Err(e) => {
- // EIO means that the process closed the other
- // end of the pty
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
+ eprintln!("pty read failed: {:?}", e);
break;
}
}
}
});
- ex.run(smol::future::or(input, output)).await;
+ let wait = async {
+ child.status_no_drop().await.unwrap();
+ };
+
+ ex.run(smol::future::or(smol::future::or(input, output), wait))
+ .await;
Ok(())
}
@@ -55,15 +56,15 @@ mod main {
#[cfg(feature = "backend-smol")]
fn main() {
- use pty_process::Command as _;
use std::os::unix::process::ExitStatusExt as _;
let status = smol::block_on(async {
- let mut child = smol::process::Command::new("sleep")
- .args(&["500"])
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
- main::run(&child).await.unwrap();
+ let pty = pty_process::smol::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd = pty_process::smol::Command::new("tac");
+ // cmd.args(&["500"]);
+ let mut child = cmd.spawn(pty).unwrap();
+ main::run(&mut child).await.unwrap();
child.status().await.unwrap()
});
std::process::exit(
diff --git a/examples/tokio.rs b/examples/tokio.rs
index 0dc007f..bc62592 100644
--- a/examples/tokio.rs
+++ b/examples/tokio.rs
@@ -32,14 +32,13 @@ mod main {
stdout.flush().await.unwrap();
}
Err(e) => {
- // EIO means that the process closed the other
- // end of the pty
- if e.raw_os_error() != Some(libc::EIO) {
- eprintln!("pty read failed: {:?}", e);
- }
+ eprintln!("pty read failed: {:?}", e);
break;
}
},
+ // _ = child.status_no_drop() => {
+ // break;
+ // }
}
}
@@ -50,13 +49,13 @@ mod main {
#[cfg(feature = "backend-tokio")]
#[tokio::main]
async fn main() {
- use pty_process::Command as _;
use std::os::unix::process::ExitStatusExt as _;
- let mut child = tokio::process::Command::new("sleep")
- .args(&["500"])
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::tokio::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd = pty_process::tokio::Command::new("tac");
+ // cmd.args(&["500"]);
+ let mut child = cmd.spawn(pty).unwrap();
main::run(&mut child).await.unwrap();
let status = child.wait().await.unwrap();
std::process::exit(
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<async_process::Child, crate::pty::async_io::Pty>;
+type Pt = async_io::Async<std::fs::File>;
+
+pub type Command = crate::Command<async_process::Command, Pt>;
+pub type Child = crate::Child<async_process::Child, Pt>;
+pub type Pty = crate::Pty<Pt>;
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<C: Impl, P: crate::pty::Impl> {
+ 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<Child<Self::Child, Self::Pty>>;
+ _phantom: ::std::marker::PhantomData<P>,
}
-impl<T> Command for T
-where
- T: Impl,
- T::Pty: crate::pty::Pty,
- <<T as Impl>::Pty as crate::pty::Pty>::Pt: ::std::os::unix::io::AsRawFd,
-{
- type Child = T::Child;
- type Pty = T::Pty;
+impl<C: Impl, P: crate::pty::Impl> Command<C, P> {
+ pub fn new<S: AsRef<::std::ffi::OsStr>>(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<T: Into<::std::process::Stdio>>(&mut self, cfg: T) {
+ self.stdin_set = true;
+ self.inner.stdin_impl(cfg.into());
+ }
+
+ pub fn stdout<T: Into<::std::process::Stdio>>(&mut self, cfg: T) {
+ self.stdout_set = true;
+ self.inner.stdout_impl(cfg.into());
+ }
+
+ pub fn stderr<T: Into<::std::process::Stdio>>(&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<Child<Self::Child, Self::Pty>> {
- let (pty, pts, stdin, stdout, stderr) = setup_pty::<Self::Pty>(size)?;
+ pty: crate::Pty<P>,
+ ) -> crate::Result<Child<<C as Impl>::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<C: Impl, P: crate::pty::Impl> ::std::ops::Deref for Command<C, P> {
+ 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<C: Impl, P: crate::pty::Impl> ::std::ops::DerefMut for Command<C, P> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
}
}
-/// Wrapper struct adding pty methods to the normal `Child` struct.
-pub struct Child<C, P> {
- child: C,
- pty: P,
+pub struct Child<C, P: crate::pty::Impl> {
+ inner: C,
+ pty: crate::Pty<P>,
}
-impl<C, P> Child<C, P>
-where
- P: crate::pty::Pty,
-{
+impl<C, P: crate::pty::Impl> Child<C, P> {
+ fn new(inner: C, pty: crate::Pty<P>) -> 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<C, P> ::std::ops::Deref for Child<C, P> {
+impl<C, P: crate::pty::Impl> ::std::ops::Deref for Child<C, P> {
type Target = C;
fn deref(&self) -> &Self::Target {
- &self.child
+ &self.inner
}
}
-impl<C, P> ::std::ops::DerefMut for Child<C, P> {
+impl<C, P: crate::pty::Impl> ::std::ops::DerefMut for Child<C, P> {
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<F>(&mut self, f: F)
where
F: FnMut() -> ::std::io::Result<()> + Send + Sync + 'static;
- fn spawn_impl(&mut self) -> ::std::io::Result<Self::Child>;
-}
-
-fn setup_pty<P>(
- 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<Self::Child>;
}
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<F>(&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::Child> {
- self.spawn()
+ fn spawn_impl(&mut self) -> crate::Result<Self::Child> {
+ 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<F>(&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::Child> {
- self.spawn()
+ fn spawn_impl(&mut self) -> crate::Result<Self::Child> {
+ 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<F>(&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::Child> {
- self.spawn()
+ fn spawn_impl(&mut self) -> crate::Result<Self::Child> {
+ 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: Impl> {
+ pt: Pt,
+ ptsname: ::std::path::PathBuf,
+}
+
+impl<Pt: Impl> Pty<Pt> {
+ pub fn new() -> crate::error::Result<Self> {
+ 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<Self>
- 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<Self>;
}
/// 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<Size> 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<std::fs::File>,
- ptsname: std::path::PathBuf,
-}
-
-impl super::Pty for Pty {
- type Pt = async_io::Async<std::fs::File>;
-
- fn new() -> crate::error::Result<Self> {
- 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<std::fs::File> {
- 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<std::fs::File> {
+ fn new_from_fh(fh: std::fs::File) -> crate::Result<Self> {
+ 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<Self> {
- 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<std::fs::File> {
- 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<Self> {
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<Self> {
+ 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<Self> {
- 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<std::fs::File> {
- 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<async_process::Child, crate::pty::async_io::Pty>;
+type Pt = async_io::Async<std::fs::File>;
+
+pub type Command = crate::Command<async_process::Command, Pt>;
+pub type Child = crate::Child<async_process::Child, Pt>;
+pub type Pty = crate::Pty<Pt>;
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<std::process::Child, crate::pty::std::Pty>;
+type Pt = std::fs::File;
+
+pub type Command = crate::Command<std::process::Command, Pt>;
+pub type Child = crate::Child<std::process::Child, Pt>;
+pub type Pty = crate::Pty<Pt>;
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<tokio::process::Child, crate::pty::tokio::Pty>;
+type Pt = crate::pty::tokio::AsyncPty;
+
+pub type Command = crate::Command<tokio::process::Command, Pt>;
+pub type Child = crate::Child<tokio::process::Child, Pt>;
+pub type Pty = crate::Pty<Pt>;
diff --git a/tests/basic.rs b/tests/basic.rs
index 9ff0a33..e164357 100644
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -1,13 +1,11 @@
-use pty_process::Command as _;
-
#[cfg(feature = "backend-std")]
#[test]
fn test_cat_std() {
use std::io::{Read as _, Write as _};
- let mut child = std::process::Command::new("cat")
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::std::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut child = pty_process::std::Command::new("cat").spawn(pty).unwrap();
child.pty().write_all(b"foo\n").unwrap();
// the pty will echo the written bytes back immediately, but the
@@ -33,9 +31,10 @@ fn test_cat_smol() {
use smol::io::{AsyncReadExt as _, AsyncWriteExt as _};
let status = smol::block_on(async {
- let mut child = smol::process::Command::new("cat")
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::smol::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut child =
+ pty_process::smol::Command::new("cat").spawn(pty).unwrap();
child.pty().write_all(b"foo\n").await.unwrap();
// the pty will echo the written bytes back immediately, but the
@@ -63,8 +62,10 @@ fn test_cat_async_std() {
use async_std::io::ReadExt as _;
let status = async_std::task::block_on(async {
- let mut child = async_std::process::Command::new("cat")
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
+ let pty = pty_process::async_std::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut child = pty_process::async_std::Command::new("cat")
+ .spawn(pty)
.unwrap();
child.pty().write_all(b"foo\n").await.unwrap();
@@ -92,9 +93,10 @@ fn test_cat_tokio() {
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
async fn async_test_cat_tokio() {
- let mut child = tokio::process::Command::new("cat")
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::tokio::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut child =
+ pty_process::tokio::Command::new("cat").spawn(pty).unwrap();
child.pty_mut().write_all(b"foo\n").await.unwrap();
// the pty will echo the written bytes back immediately, but the
diff --git a/tests/pipe.rs b/tests/pipe.rs
new file mode 100644
index 0000000..b1e0540
--- /dev/null
+++ b/tests/pipe.rs
@@ -0,0 +1,68 @@
+#[test]
+fn test_pipe_basic() {
+ use std::os::unix::io::FromRawFd as _;
+
+ let (read_fd, write_fd) = nix::unistd::pipe().unwrap();
+
+ let mut child_from = std::process::Command::new("seq");
+ child_from.args(["1", "10"]);
+ child_from.stdout(unsafe { std::process::Stdio::from_raw_fd(write_fd) });
+
+ let mut child_to = std::process::Command::new("tac");
+ child_to.stdin(unsafe { std::process::Stdio::from_raw_fd(read_fd) });
+ child_to.stdout(std::process::Stdio::piped());
+
+ assert!(child_from.status().unwrap().success());
+ nix::unistd::close(write_fd).unwrap();
+ let output = child_to.output().unwrap();
+ assert!(output.status.success());
+ assert_eq!(output.stdout, b"10\n9\n8\n7\n6\n5\n4\n3\n2\n1\n");
+}
+
+#[cfg(feature = "backend-async-std")]
+// TODO (hangs because i'm still overriding the configured fds)
+// #[test]
+fn test_pipe_async() {
+ use async_std::io::ReadExt as _;
+ use std::os::unix::io::FromRawFd as _;
+
+ let (status_from, status_to) = async_std::task::block_on(async {
+ let (read_fd, write_fd) = nix::unistd::pipe().unwrap();
+
+ let pty_from = pty_process::async_std::Pty::new().unwrap();
+ pty_from.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd_from = pty_process::async_std::Command::new("seq");
+ cmd_from.args(["1", "10"]);
+ cmd_from
+ .stdout(unsafe { std::process::Stdio::from_raw_fd(write_fd) });
+ let mut child_from = cmd_from.spawn(pty_from).unwrap();
+
+ let pty_to = pty_process::async_std::Pty::new().unwrap();
+ pty_to.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd_to = pty_process::async_std::Command::new("tac");
+ cmd_to.stdin(unsafe { std::process::Stdio::from_raw_fd(read_fd) });
+ let mut child_to = cmd_to.spawn(pty_to).unwrap();
+
+ // the pty will echo the written bytes back immediately, but the
+ // subprocess needs to generate its own output, which takes time, so
+ // we can't just read immediately (we may just get the echoed bytes).
+ // because the output generation is happening in the subprocess, we
+ // also don't have any way to know when (or if!) the subprocess will
+ // decide to send its output, so sleeping is the best we can do.
+ async_std::task::sleep(std::time::Duration::from_secs(1)).await;
+
+ let mut buf = [0u8; 1024];
+ let bytes = child_to.pty().read(&mut buf).await.unwrap();
+ assert_eq!(
+ &buf[..bytes],
+ b"10\r\n9\r\n8\r\n7\r\n6\r\n5\r\n4\r\n3\r\n2\r\n1\r\n"
+ );
+
+ (
+ child_from.status().await.unwrap(),
+ child_to.status().await.unwrap(),
+ )
+ });
+ assert_eq!(status_from.code().unwrap(), 0);
+ assert_eq!(status_to.code().unwrap(), 0);
+}
diff --git a/tests/winch.rs b/tests/winch.rs
index 234afeb..326efcc 100644
--- a/tests/winch.rs
+++ b/tests/winch.rs
@@ -1,22 +1,22 @@
-use pty_process::Command as _;
use std::io::{Read as _, Write as _};
#[cfg(feature = "backend-std")]
#[test]
fn test_winch() {
- let mut child = std::process::Command::new("perl")
- .args(&[
- "-E",
- "$|++; $SIG{WINCH} = sub { say 'WINCH' }; say 'started'; <>",
- ])
- .spawn_pty(Some(&pty_process::Size::new(24, 80)))
- .unwrap();
+ let pty = pty_process::std::Pty::new().unwrap();
+ pty.resize(pty_process::Size::new(24, 80)).unwrap();
+ let mut cmd = pty_process::std::Command::new("perl");
+ cmd.args(&[
+ "-E",
+ "$|++; $SIG{WINCH} = sub { say 'WINCH' }; say 'started'; <>",
+ ]);
+ let mut child = cmd.spawn(pty).unwrap();
let mut buf = [0u8; 1024];
let bytes = child.pty().read(&mut buf).unwrap();
assert_eq!(&buf[..bytes], b"started\r\n");
- child.resize_pty(&pty_process::Size::new(25, 80)).unwrap();
+ child.resize_pty(pty_process::Size::new(25, 80)).unwrap();
let bytes = child.pty().read(&mut buf).unwrap();
assert_eq!(&buf[..bytes], b"WINCH\r\n");