#![deny(missing_docs)]
extern crate futures;
extern crate libc;
extern crate mio;
extern crate tokio;
extern crate tokio_signal;
use futures::{Async, Future, Poll, Stream};
use futures::future::FlattenStream;
use libc::c_int;
use mio::unix::{EventedFd, UnixReady};
use mio::{PollOpt, Ready, Token};
use mio::event::Evented;
use std::ffi::{CStr, OsStr};
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::{AsyncWrite, AsyncRead};
use tokio_signal::unix::Signal;
use tokio_signal::IoFuture;
use tokio::reactor::{PollEvented2};
#[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 {
let fd = 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());
}
File::from_raw_fd(fd)
};
Ok(AsyncPtyMaster(PollEvented2::new(AsyncPtyFile::new(inner))))
}
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(target_os = "macos"))]
{
if unsafe { libc::ptsname_r(fd, buf.as_mut_ptr(), buf.len()) } != 0 {
return Err(io::Error::last_os_error());
}
}
#[cfg(target_os = "macos")]
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());
}
}
}
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)
}
}