#![deny(missing_docs)]
#![doc(html_root_url = "https://docs.rs/tokio-pty-process/0.4.0")]
extern crate bytes;
#[macro_use]
extern crate futures;
extern crate libc;
extern crate mio;
extern crate tokio;
extern crate tokio_io;
extern crate tokio_signal;
use futures::future::FlattenStream;
use futures::{Async, Future, Poll, Stream};
use libc::{c_int, c_ushort};
use mio::event::Evented;
use mio::unix::{EventedFd, UnixReady};
use mio::{PollOpt, Ready, Token};
use std::ffi::{CStr, OsStr, OsString};
use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::mem;
use std::os::unix::prelude::*;
use std::os::unix::process::CommandExt as StdUnixCommandExt;
use std::process::{self, ExitStatus};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::reactor::PollEvented2;
use tokio_signal::unix::Signal;
use tokio_signal::IoFuture;
mod split;
pub use split::{AsyncPtyMasterReadHalf, AsyncPtyMasterWriteHalf};
#[derive(Debug)]
struct AsyncPtyFile(File);
impl AsyncPtyFile {
pub fn new(inner: File) -> Self {
AsyncPtyFile(inner)
}
}
impl Read for AsyncPtyFile {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
self.0.read(bytes)
}
}
impl Write for AsyncPtyFile {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.0.write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl Evented for AsyncPtyFile {
fn register(
&self,
poll: &mio::Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> io::Result<()> {
EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | UnixReady::hup(), opts)
}
fn reregister(
&self,
poll: &mio::Poll,
token: Token,
interest: Ready,
opts: PollOpt,
) -> io::Result<()> {
EventedFd(&self.0.as_raw_fd()).reregister(poll, token, interest | UnixReady::hup(), opts)
}
fn deregister(&self, poll: &mio::Poll) -> io::Result<()> {
EventedFd(&self.0.as_raw_fd()).deregister(poll)
}
}
pub struct AsyncPtyMaster(PollEvented2<AsyncPtyFile>);
impl AsyncPtyMaster {
pub fn open() -> Result<Self, io::Error> {
let inner = unsafe {
const APPLY_NONBLOCK_AFTER_OPEN: bool = cfg!(target_os = "freebsd");
let fd = if APPLY_NONBLOCK_AFTER_OPEN {
libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY)
} else {
libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY | libc::O_NONBLOCK)
};
if fd < 0 {
return Err(io::Error::last_os_error());
}
if libc::grantpt(fd) != 0 {
return Err(io::Error::last_os_error());
}
if libc::unlockpt(fd) != 0 {
return Err(io::Error::last_os_error());
}
if APPLY_NONBLOCK_AFTER_OPEN {
let flags = libc::fcntl(fd, libc::F_GETFL, 0);
if flags < 0 {
return Err(io::Error::last_os_error());
}
if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) == -1 {
return Err(io::Error::last_os_error());
}
}
File::from_raw_fd(fd)
};
Ok(AsyncPtyMaster(PollEvented2::new(AsyncPtyFile::new(inner))))
}
pub fn split(self) -> (AsyncPtyMasterReadHalf, AsyncPtyMasterWriteHalf) {
split::split(self)
}
fn open_sync_pty_slave(&self) -> Result<File, io::Error> {
let mut buf: [libc::c_char; 512] = [0; 512];
let fd = self.as_raw_fd();
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
{
if unsafe { libc::ptsname_r(fd, buf.as_mut_ptr(), buf.len()) } != 0 {
return Err(io::Error::last_os_error());
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
unsafe {
let st = libc::ptsname(fd);
if st.is_null() {
return Err(io::Error::last_os_error());
}
libc::strncpy(buf.as_mut_ptr(), st, buf.len());
}
let ptsname = OsStr::from_bytes(unsafe { CStr::from_ptr(&buf as _) }.to_bytes());
OpenOptions::new().read(true).write(true).open(ptsname)
}
}
impl AsRawFd for AsyncPtyMaster {
fn as_raw_fd(&self) -> RawFd {
self.0.get_ref().0.as_raw_fd()
}
}
impl Read for AsyncPtyMaster {
fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
self.0.read(bytes)
}
}
impl AsyncRead for AsyncPtyMaster {}
impl Write for AsyncPtyMaster {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.0.write(bytes)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl AsyncWrite for AsyncPtyMaster {
fn shutdown(&mut self) -> Poll<(), io::Error> {
self.0.shutdown()
}
}
#[must_use = "futures do nothing unless polled"]
pub struct Child {
inner: process::Child,
kill_on_drop: bool,
reaped: bool,
sigchld: FlattenStream<IoFuture<Signal>>,
}
impl fmt::Debug for Child {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Child")
.field("pid", &self.inner.id())
.field("inner", &self.inner)
.field("kill_on_drop", &self.kill_on_drop)
.field("reaped", &self.reaped)
.field("sigchld", &"..")
.finish()
}
}
impl Child {
fn new(inner: process::Child) -> Child {
Child {
inner: inner,
kill_on_drop: true,
reaped: false,
sigchld: Signal::new(libc::SIGCHLD).flatten_stream(),
}
}
pub fn id(&self) -> u32 {
self.inner.id()
}
pub fn kill(&mut self) -> io::Result<()> {
if self.reaped {
Ok(())
} else {
self.inner.kill()
}
}
pub fn forget(mut self) {
self.kill_on_drop = false;
}
pub fn poll_exit(&mut self) -> Poll<ExitStatus, io::Error> {
assert!(!self.reaped);
loop {
if let Some(e) = self.try_wait()? {
self.reaped = true;
return Ok(e.into());
}
if self.sigchld.poll()?.is_not_ready() {
return Ok(Async::NotReady);
}
}
}
fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
let id = self.id() as c_int;
let mut status = 0;
loop {
match unsafe { libc::waitpid(id, &mut status, libc::WNOHANG) } {
0 => return Ok(None),
n if n < 0 => {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(err);
}
n => {
assert_eq!(n, id);
return Ok(Some(ExitStatus::from_raw(status)));
}
}
}
}
}
impl Future for Child {
type Item = ExitStatus;
type Error = io::Error;
fn poll(&mut self) -> Poll<ExitStatus, io::Error> {
self.poll_exit()
}
}
impl Drop for Child {
fn drop(&mut self) {
if self.kill_on_drop {
drop(self.kill());
}
}
}
pub struct AsyncPtyFd<T: AsAsyncPtyFd>(T);
impl<T: AsAsyncPtyFd> AsyncPtyFd<T> {
pub fn from(inner: T) -> Self {
AsyncPtyFd(inner)
}
}
impl<T: AsAsyncPtyFd> Future for AsyncPtyFd<T> {
type Item = RawFd;
type Error = io::Error;
fn poll(&mut self) -> Poll<RawFd, io::Error> {
self.0.as_async_pty_fd()
}
}
pub trait AsAsyncPtyFd {
fn as_async_pty_fd(&self) -> Poll<RawFd, io::Error>;
}
impl AsAsyncPtyFd for AsyncPtyMaster {
fn as_async_pty_fd(&self) -> Poll<RawFd, io::Error> {
Ok(Async::Ready(self.as_raw_fd()))
}
}
pub trait PtyMaster {
fn ptsname(&self) -> Poll<OsString, io::Error>;
fn resize(&self, rows: c_ushort, cols: c_ushort) -> Poll<(), io::Error>;
fn winsize(&self) -> Poll<(c_ushort, c_ushort), io::Error>;
}
impl<T: AsAsyncPtyFd> PtyMaster for T {
fn ptsname(&self) -> Poll<OsString, io::Error> {
let mut buf: [libc::c_char; 512] = [0; 512];
let fd = try_ready!(self.as_async_pty_fd());
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
{
if unsafe { libc::ptsname_r(fd, buf.as_mut_ptr(), buf.len()) } != 0 {
return Err(io::Error::last_os_error());
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
unsafe {
let st = libc::ptsname(fd);
if st.is_null() {
return Err(io::Error::last_os_error());
}
libc::strncpy(buf.as_mut_ptr(), st, buf.len());
}
let ptsname = OsStr::from_bytes(unsafe { CStr::from_ptr(&buf as _) }.to_bytes());
Ok(Async::Ready(ptsname.to_os_string()))
}
fn winsize(&self) -> Poll<(c_ushort, c_ushort), io::Error> {
let fd = try_ready!(self.as_async_pty_fd());
let mut winsz: libc::winsize = unsafe { std::mem::zeroed() };
if unsafe { libc::ioctl(fd, libc::TIOCGWINSZ.into(), &mut winsz) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Async::Ready((winsz.ws_row, winsz.ws_col)))
}
fn resize(&self, rows: c_ushort, cols: c_ushort) -> Poll<(), io::Error> {
let fd = try_ready!(self.as_async_pty_fd());
let winsz = libc::winsize {
ws_row: rows,
ws_col: cols,
ws_xpixel: 0,
ws_ypixel: 0,
};
if unsafe { libc::ioctl(fd, libc::TIOCSWINSZ.into(), &winsz) } != 0 {
return Err(io::Error::last_os_error());
}
Ok(Async::Ready(()))
}
}
trait CommandExtInternal {
fn spawn_pty_async_full(&mut self, ptymaster: &AsyncPtyMaster, raw: bool) -> io::Result<Child>;
}
impl CommandExtInternal for process::Command {
fn spawn_pty_async_full(&mut self, ptymaster: &AsyncPtyMaster, raw: bool) -> io::Result<Child> {
let master_fd = ptymaster.as_raw_fd();
let slave = ptymaster.open_sync_pty_slave()?;
let slave_fd = slave.as_raw_fd();
self.stdin(slave.try_clone()?);
self.stdout(slave.try_clone()?);
self.stderr(slave);
self.before_exec(move || {
unsafe {
if raw {
let mut attrs: libc::termios = mem::zeroed();
if libc::tcgetattr(slave_fd, &mut attrs as _) != 0 {
return Err(io::Error::last_os_error());
}
libc::cfmakeraw(&mut attrs as _);
if libc::tcsetattr(slave_fd, libc::TCSANOW, &attrs as _) != 0 {
return Err(io::Error::last_os_error());
}
}
if libc::close(master_fd) != 0 {
return Err(io::Error::last_os_error());
}
if libc::setsid() < 0 {
return Err(io::Error::last_os_error());
}
if libc::ioctl(0, libc::TIOCSCTTY.into(), 1) != 0 {
return Err(io::Error::last_os_error());
}
}
Ok(())
});
Ok(Child::new(self.spawn()?))
}
}
pub trait CommandExt {
fn spawn_pty_async(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child>;
fn spawn_pty_async_raw(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child>;
}
impl CommandExt for process::Command {
fn spawn_pty_async(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child> {
self.spawn_pty_async_full(ptymaster, false)
}
fn spawn_pty_async_raw(&mut self, ptymaster: &AsyncPtyMaster) -> io::Result<Child> {
self.spawn_pty_async_full(ptymaster, true)
}
}
#[cfg(test)]
mod tests {
extern crate errno;
extern crate libc;
use super::*;
#[test]
fn basic_nonblocking() {
let master = AsyncPtyMaster::open().unwrap();
let fd = master.as_raw_fd();
let mut buf = [0u8; 128];
let rval = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 128) };
let errno: i32 = errno::errno().into();
assert_eq!(rval, -1);
assert_eq!(errno, libc::EWOULDBLOCK as i32);
}
struct GetSize<'a, T: PtyMaster>(&'a T);
impl<'a, T: PtyMaster> Future for GetSize<'a, T> {
type Item = (c_ushort, c_ushort);
type Error = std::io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.0.winsize()
}
}
struct Resize<'a, T: PtyMaster> {
pty: &'a T,
rows: c_ushort,
cols: c_ushort,
}
impl<'a, T: PtyMaster> Future for Resize<'a, T> {
type Item = ();
type Error = std::io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
self.pty.resize(self.rows, self.cols)
}
}
#[test]
fn test_winsize() {
let master = AsyncPtyMaster::open().expect("Could not open the PTY");
#[cfg(target_os = "macos")]
let mut child = std::process::Command::new("cat")
.spawn_pty_async(&master)
.expect("Could not spawn child");
Resize {
pty: &master,
cols: 80,
rows: 50,
}
.wait()
.expect("Could not resize the PTY");
let (rows, cols) = GetSize(&master).wait().expect("Could not get PTY size");
assert_eq!(cols, 80);
assert_eq!(rows, 50);
#[cfg(target_os = "macos")]
child.kill().expect("Could not kill child");
}
}