Skip to content

Commit

Permalink
pty: Add forkpty
Browse files Browse the repository at this point in the history
  • Loading branch information
keur committed Apr 23, 2019
1 parent 0059ddf commit dcbc3c8
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#969](https://github.com/nix-rust/nix/pull/969))
- Add several errno constants from OpenBSD 6.2
([#1036](https://github.com/nix-rust/nix/pull/1036))
- 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/))
Expand Down
59 changes: 59 additions & 0 deletions src/pty.rs
Expand Up @@ -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;

Expand All @@ -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
///
Expand Down Expand Up @@ -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,
})
}

36 changes: 34 additions & 2 deletions test/test_pty.rs
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
},
}
}

0 comments on commit dcbc3c8

Please sign in to comment.