Skip to content

Commit 71b35a0

Browse files
bors[bot]kkuehlz
andcommitted
Merge #1042
1042: pty: Add forkpty r=asomers a=keur Co-authored-by: Kevin Kuehler <[email protected]>
2 parents d8f3f74 + 26bd036 commit 71b35a0

File tree

3 files changed

+95
-2
lines changed

3 files changed

+95
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2424
([#1044](https://github.com/nix-rust/nix/pull/1044))
2525
- Added a `access` wrapper
2626
([#1045](https://github.com/nix-rust/nix/pull/1045))
27+
- Add `forkpty`
28+
([#1042](https://github.com/nix-rust/nix/pull/1042))
2729

2830
### Changed
2931
- `PollFd` event flags renamed to `PollFlags` ([#1024](https://github.com/nix-rust/nix/pull/1024/))

src/pty.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::mem;
1010
use std::os::unix::prelude::*;
1111

1212
use sys::termios::Termios;
13+
use unistd::ForkResult;
1314
use {Result, Error, fcntl};
1415
use errno::Errno;
1516

@@ -26,6 +27,18 @@ pub struct OpenptyResult {
2627
pub slave: RawFd,
2728
}
2829

30+
/// Representation of a master with a forked pty
31+
///
32+
/// This is returned by `forkpty`. Note that this type does *not* implement `Drop`, so the user
33+
/// must manually close the file descriptors.
34+
#[derive(Clone, Copy, Debug)]
35+
pub struct ForkptyResult {
36+
/// The master port in a virtual pty pair
37+
pub master: RawFd,
38+
/// Metadata about forked process
39+
pub fork_result: ForkResult,
40+
}
41+
2942

3043
/// Representation of the Master device in a master/slave pty pair
3144
///
@@ -266,3 +279,49 @@ pub fn openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>
266279
slave: slave,
267280
})
268281
}
282+
283+
/// Create a new pseudoterminal, returning the master file descriptor and forked pid.
284+
/// in `ForkptyResult`
285+
/// (see [`forkpty`](http://man7.org/linux/man-pages/man3/forkpty.3.html)).
286+
///
287+
/// If `winsize` is not `None`, the window size of the slave will be set to
288+
/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's
289+
/// terminal settings of the slave will be set to the values in `termios`.
290+
pub fn forkpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(
291+
winsize: T,
292+
termios: U,
293+
) -> Result<ForkptyResult> {
294+
use std::ptr;
295+
use unistd::Pid;
296+
use unistd::ForkResult::*;
297+
298+
let mut master: libc::c_int = unsafe { mem::uninitialized() };
299+
300+
let term = match termios.into() {
301+
Some(termios) => {
302+
let inner_termios = termios.get_libc_termios();
303+
&*inner_termios as *const libc::termios as *mut _
304+
},
305+
None => ptr::null_mut(),
306+
};
307+
308+
let win = winsize
309+
.into()
310+
.map(|ws| ws as *const Winsize as *mut _)
311+
.unwrap_or(ptr::null_mut());
312+
313+
let res = unsafe {
314+
libc::forkpty(&mut master, ptr::null_mut(), term, win)
315+
};
316+
317+
let fork_result = Errno::result(res).map(|res| match res {
318+
0 => Child,
319+
res => Parent { child: Pid::from_raw(res) },
320+
})?;
321+
322+
Ok(ForkptyResult {
323+
master: master,
324+
fork_result: fork_result,
325+
})
326+
}
327+

test/test_pty.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ use std::path::Path;
33
use std::os::unix::prelude::*;
44
use tempfile::tempfile;
55

6+
use libc::{_exit, STDOUT_FILENO};
67
use nix::fcntl::{OFlag, open};
78
use nix::pty::*;
89
use nix::sys::stat;
910
use nix::sys::termios::*;
10-
use nix::unistd::{write, close};
11+
use nix::unistd::{write, close, pause};
1112

1213
/// Regression test for Issue #659
1314
/// This is the correct way to explicitly close a `PtyMaster`
@@ -100,7 +101,7 @@ fn test_ptsname_unique() {
100101
/// this test we perform the basic act of getting a file handle for a connect master/slave PTTY
101102
/// pair.
102103
#[test]
103-
fn test_open_ptty_pair() {
104+
fn test_open_ptty_pair() {
104105
let _m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
105106

106107
// Open a new PTTY master
@@ -201,3 +202,34 @@ fn test_openpty_with_termios() {
201202
close(pty.master).unwrap();
202203
close(pty.slave).unwrap();
203204
}
205+
206+
#[test]
207+
fn test_forkpty() {
208+
use nix::unistd::ForkResult::*;
209+
use nix::sys::signal::*;
210+
use nix::sys::wait::wait;
211+
// forkpty calls openpty which uses ptname(3) internally.
212+
let _m0 = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
213+
// forkpty spawns a child process
214+
let _m1 = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
215+
216+
let string = "naninani\n";
217+
let echoed_string = "naninani\r\n";
218+
let pty = forkpty(None, None).unwrap();
219+
match pty.fork_result {
220+
Child => {
221+
write(STDOUT_FILENO, string.as_bytes()).unwrap();
222+
pause(); // we need the child to stay alive until the parent calls read
223+
unsafe { _exit(0); }
224+
},
225+
Parent { child } => {
226+
let mut buf = [0u8; 10];
227+
assert!(child.as_raw() > 0);
228+
::read_exact(pty.master, &mut buf);
229+
kill(child, SIGTERM).unwrap();
230+
wait().unwrap(); // keep other tests using generic wait from getting our child
231+
assert_eq!(&buf, echoed_string.as_bytes());
232+
close(pty.master).unwrap();
233+
},
234+
}
235+
}

0 commit comments

Comments
 (0)