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();
+ },
+ }
+}