diff options
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | src/pty.rs | 59 | ||||
-rw-r--r-- | test/test_pty.rs | 36 |
3 files changed, 95 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c52fd527..aa741580 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/)) @@ -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 4f428bed..476b15c1 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(); + }, + } +} |