diff --git a/CHANGELOG.md b/CHANGELOG.md
index c52fd5271c..aa741580f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   ([#1044](https://github.com/nix-rust/nix/pull/1044))
 - Added a `access` wrapper
   ([#1045](https://github.com/nix-rust/nix/pull/1045))
+- Add `forkpty`
+  ([#1042](https://github.com/nix-rust/nix/pull/1042))
 
 ### Changed
 - `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/))
diff --git a/src/pty.rs b/src/pty.rs
index b71718850e..41bd51b68a 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,18 @@ 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, Debug)]
+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 +279,49 @@ pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>
         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`.
+pub fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
+    winsize: T,
+    termios: U,
+) -> Result<ForkptyResult> {
+    use std::ptr;
+    use unistd::Pid;
+    use unistd::ForkResult::*;
+
+    let mut master: libc::c_int = unsafe { mem::uninitialized() };
+
+    let term = match termios.into() {
+        Some(termios) => {
+            let inner_termios = termios.get_libc_termios();
+            &*inner_termios as *const libc::termios as *mut _
+        },
+        None => ptr::null_mut(),
+    };
+
+    let win = winsize
+        .into()
+        .map(|ws| ws as *const Winsize as *mut _)
+        .unwrap_or(ptr::null_mut());
+
+    let res = unsafe {
+        libc::forkpty(&mut master, ptr::null_mut(), term, win)
+    };
+
+    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..476b15c101 100644
--- a/test/test_pty.rs
+++ b/test/test_pty.rs
@@ -3,11 +3,12 @@ use std::path::Path;
 use std::os::unix::prelude::*;
 use tempfile::tempfile;
 
+use libc::{_exit, STDOUT_FILENO};
 use nix::fcntl::{OFlag, open};
 use nix::pty::*;
 use nix::sys::stat;
 use nix::sys::termios::*;
-use nix::unistd::{write, close};
+use nix::unistd::{write, close, pause};
 
 /// Regression test for Issue #659
 /// This is the correct way to explicitly close a `PtyMaster`
@@ -100,7 +101,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 +202,34 @@ fn test_openpty_with_termios() {
     close(pty.master).unwrap();
     close(pty.slave).unwrap();
 }
+
+#[test]
+fn test_forkpty() {
+    use nix::unistd::ForkResult::*;
+    use nix::sys::signal::*;
+    use nix::sys::wait::wait;
+    // 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(STDOUT_FILENO, string.as_bytes()).unwrap();
+            pause();  // we need the child to stay alive until the parent calls read
+            unsafe { _exit(0); }
+        },
+        Parent { child } => {
+            let mut buf = [0u8; 10];
+            assert!(child.as_raw() > 0);
+            ::read_exact(pty.master, &mut buf);
+            kill(child, SIGTERM).unwrap();
+            wait().unwrap(); // keep other tests using generic wait from getting our child
+            assert_eq!(&buf, echoed_string.as_bytes());
+            close(pty.master).unwrap();
+        },
+    }
+}