diff --git a/src/pty.rs b/src/pty.rs index b71718850e..0001d12551 100644 --- a/src/pty.rs +++ b/src/pty.rs @@ -10,6 +10,7 @@ use std::mem; use std::os::unix::prelude::*; use sys::termios::Termios; +use unistd::ForkResult; use {Result, Error, fcntl}; use errno::Errno; @@ -26,6 +27,19 @@ pub struct OpenptyResult { pub slave: RawFd, } +/// Representation of a master with a forked pty +/// +/// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user +/// must manually close the file descriptors. +#[derive(Clone, Copy)] +#[allow(missing_debug_implementations)] +pub struct ForkptyResult { + /// The master port in a virtual pty pair + pub master: RawFd, + /// Metadata about forked process + pub fork_result: ForkResult, +} + /// Representation of the Master device in a master/slave pty pair /// @@ -266,3 +280,76 @@ pub fn openpty<'a, 'b, T: Into>, U: Into slave: slave, }) } + +/// Create a new pseudoterminal, returning the master file descriptor and forked pid. +/// in `ForkptyResult` +/// (see [`forkpty`](http://man7.org/linux/man-pages/man3/forkpty.3.html)). +/// +/// If `winsize` is not `None`, the window size of the slave will be set to +/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's +/// terminal settings of the slave will be set to the values in `termios`. +#[inline] +pub fn forkpty<'a, 'b, T: Into>, U: Into>>(winsize: T, termios: U) -> Result { + use std::ptr; + use unistd::Pid; + use unistd::ForkResult::*; + + let mut master: libc::c_int = unsafe { mem::uninitialized() }; + let res = { + match (termios.into(), winsize.into()) { + (Some(termios), Some(winsize)) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::forkpty( + &mut master, + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + winsize as *const Winsize as *mut _, + ) + } + } + (None, Some(winsize)) => { + unsafe { + libc::forkpty( + &mut master, + ptr::null_mut(), + ptr::null_mut(), + winsize as *const Winsize as *mut _, + ) + } + } + (Some(termios), None) => { + let inner_termios = termios.get_libc_termios(); + unsafe { + libc::forkpty( + &mut master, + ptr::null_mut(), + &*inner_termios as *const libc::termios as *mut _, + ptr::null_mut(), + ) + } + } + (None, None) => { + unsafe { + libc::forkpty( + &mut master, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + } + } + } + }; + + let fork_result = Errno::result(res).map(|res| match res { + 0 => Child, + res => Parent { child: Pid::from_raw(res) }, + })?; + + Ok(ForkptyResult { + master: master, + fork_result: fork_result, + }) +} + diff --git a/test/test_pty.rs b/test/test_pty.rs index 4f428bed9a..8f04aecb8c 100644 --- a/test/test_pty.rs +++ b/test/test_pty.rs @@ -3,10 +3,12 @@ use std::path::Path; use std::os::unix::prelude::*; use tempfile::tempfile; +use libc::_exit; use nix::fcntl::{OFlag, open}; use nix::pty::*; use nix::sys::stat; use nix::sys::termios::*; +use nix::sys::wait::wait; use nix::unistd::{write, close}; /// Regression test for Issue #659 @@ -100,7 +102,7 @@ fn test_ptsname_unique() { /// this test we perform the basic act of getting a file handle for a connect master/slave PTTY /// pair. #[test] -fn test_open_ptty_pair() { +fn test_open_ptty_pair() { let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); // Open a new PTTY master @@ -201,3 +203,30 @@ fn test_openpty_with_termios() { close(pty.master).unwrap(); close(pty.slave).unwrap(); } + +#[test] +fn test_forkpty() { + use nix::unistd::ForkResult::*; + // forkpty calls openpty which uses ptname(3) internally. + let _m0 = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test"); + // forkpty spawns a child process + let _m1 = ::FORK_MTX.lock().expect("Mutex got poisoned by another test"); + + let string = "naninani\n"; + let echoed_string = "naninani\r\n"; + let pty = forkpty(None, None).unwrap(); + match pty.fork_result { + Child => { + write(0, string.as_bytes()).unwrap(); + unsafe { _exit(0) } + }, + Parent { child } => { + wait().unwrap(); // keep other tests using generic wait from getting our child + let mut buf = [0u8; 10]; + assert!(child.as_raw() > 0); + ::read_exact(pty.master, &mut buf); + assert_eq!(&buf, echoed_string.as_bytes()); + close(pty.master).unwrap(); + }, + } +}