#![feature(process_exec)]
#[macro_use]
extern crate chan;
extern crate chan_signal;
extern crate fd;
extern crate libc;
extern crate termios;
use chan_signal::Signal;
use fd::{Pipe, set_flags, splice_loop, unset_append_flag};
use ffi::{get_winsize, openpty, set_winsize};
use libc::c_int;
use std::fs::File;
use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use termios::{Termios, tcsetattr};
pub use fd::FileDesc;
pub mod ffi;
pub struct TtyServer {
master: File,
slave: Option<File>,
path: PathBuf,
}
pub struct TtyClient {
#[allow(dead_code)]
master: FileDesc,
master_status: Option<c_int>,
peer: FileDesc,
peer_status: Option<c_int>,
termios_orig: Termios,
do_flush: Arc<AtomicBool>,
flush_event: Receiver<()>,
_stop: chan::Sender<()>,
}
impl TtyServer {
pub fn new<T>(template: Option<&T>) -> io::Result<TtyServer> where T: AsRawFd {
let pty = match template {
Some(t) => try!(openpty(Some(&try!(Termios::from_fd(t.as_raw_fd()))), Some(&try!(get_winsize(t))))),
None => try!(openpty(None, None)),
};
Ok(TtyServer {
master: pty.master,
slave: Some(pty.slave),
path: pty.path,
})
}
pub fn new_client<T>(&self, peer: T, sigwinch_handler: Option<chan::Receiver<Signal>>) ->
io::Result<TtyClient> where T: AsRawFd + IntoRawFd {
let master = FileDesc::new(self.master.as_raw_fd(), false);
TtyClient::new(master, peer, sigwinch_handler)
}
pub fn get_master(&self) -> &File {
&self.master
}
pub fn take_slave(&mut self) -> Option<File> {
self.slave.take()
}
pub fn spawn(&mut self, mut cmd: Command) -> io::Result<Child> {
match self.slave.take() {
Some(slave) => {
cmd.stdin(unsafe { Stdio::from_raw_fd(slave.as_raw_fd()) }).
stdout(unsafe { Stdio::from_raw_fd(slave.as_raw_fd()) }).
stderr(unsafe { Stdio::from_raw_fd(slave.into_raw_fd()) }).
before_exec(|| { let _ = unsafe { libc::setsid() }; Ok(()) }).
spawn()
},
None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "No TTY slave")),
}
}
}
impl AsRef<Path> for TtyServer {
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}
fn copy_winsize<T, U>(src: &T, dst: &U) where T: AsRawFd, U: AsRawFd {
if let Ok(ws) = get_winsize(src) {
let _ = set_winsize(dst, &ws);
}
}
impl TtyClient {
pub fn new<T, U>(master: T, peer: U, sigwinch_handler: Option<chan::Receiver<Signal>>) ->
io::Result<TtyClient> where T: AsRawFd + IntoRawFd, U: AsRawFd + IntoRawFd {
let termios_orig = try!(Termios::from_fd(peer.as_raw_fd()));
let mut termios_peer = try!(Termios::from_fd(peer.as_raw_fd()));
termios_peer.c_lflag &= !(termios::ECHO | termios::ICANON | termios::ISIG);
termios_peer.c_iflag &= !(termios::IGNBRK | termios::ICRNL);
termios_peer.c_iflag |= termios::BRKINT;
termios_peer.c_cc[termios::VMIN] = 1;
termios_peer.c_cc[termios::VTIME] = 0;
try!(tcsetattr(peer.as_raw_fd(), termios::TCSAFLUSH, &termios_peer));
let do_flush_main = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx): (Sender<()>, Receiver<()>) = channel();
let (m2p_tx, m2p_rx) = match Pipe::new() {
Ok(p) => (p.writer, p.reader),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
};
let do_flush = do_flush_main.clone();
let master_fd = master.as_raw_fd();
thread::spawn(move || splice_loop(do_flush, None, master_fd, m2p_tx.as_raw_fd()));
let do_flush = do_flush_main.clone();
let peer_fd = peer.as_raw_fd();
let peer_status = try!(unset_append_flag(peer_fd));
thread::spawn(move || splice_loop(do_flush, None, m2p_rx.as_raw_fd(), peer_fd));
let (p2m_tx, p2m_rx) = match Pipe::new() {
Ok(p) => (p.writer, p.reader),
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
};
let do_flush = do_flush_main.clone();
let peer_fd = peer.as_raw_fd();
thread::spawn(move || splice_loop(do_flush, None, peer_fd, p2m_tx.as_raw_fd()));
let do_flush = do_flush_main.clone();
let master_fd = master.as_raw_fd();
let master_status = try!(unset_append_flag(master_fd));
thread::spawn(move || splice_loop(do_flush, Some(event_tx), p2m_rx.as_raw_fd(), master_fd));
let (stop_tx, stop_rx) = chan::sync(0);
if let Some(signal) = sigwinch_handler {
let master2 = FileDesc::new(master.as_raw_fd(), false);
let peer2 = FileDesc::new(peer.as_raw_fd(), false);
thread::spawn(move || {
'select: loop {
chan_select! {
signal.recv() -> signal => {
if signal != Some(Signal::WINCH) {
continue 'select;
}
copy_winsize(&peer2, &master2);
},
stop_rx.recv() => {
break;
}
}
}
});
}
Ok(TtyClient {
master: FileDesc::new(master.into_raw_fd(), true),
master_status: master_status,
peer: FileDesc::new(peer.into_raw_fd(), true),
peer_status: peer_status,
termios_orig: termios_orig,
do_flush: do_flush_main,
flush_event: event_rx,
_stop: stop_tx,
})
}
pub fn wait(&self) {
while !self.do_flush.load(Relaxed) {
let _ = self.flush_event.recv();
}
}
pub fn update_winsize(&mut self) {
copy_winsize(&self.peer, &self.master);
}
}
impl Drop for TtyClient {
fn drop(&mut self) {
self.do_flush.store(true, Relaxed);
let _ = tcsetattr(self.peer.as_raw_fd(), termios::TCSAFLUSH, &self.termios_orig);
let tty_fd = [(&self.peer, self.peer_status), (&self.master, self.master_status)];
for &(fd, status) in tty_fd.iter() {
if let Some(s) = status {
let _ = set_flags(fd.as_raw_fd(), s);
}
}
}
}