Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add openpty #456

Merged
merged 1 commit into from
Jun 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
([#551](https://github.com/nix-rust/nix/pull/551))
- Added `nix::pty::{grantpt, posix_openpt, ptsname/ptsname_r, unlockpt}`
([#556](https://github.com/nix-rust/nix/pull/556)
- Added `nix::ptr::openpty`
([#456](https://github.com/nix-rust/nix/pull/456))

### Changed
- Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe.
Expand Down
56 changes: 54 additions & 2 deletions src/pty.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
//! Create master and slave virtual pseudo-terminals (PTYs)
use libc;

pub use libc::pid_t as SessionId;
pub use libc::winsize as Winsize;

use std::ffi::CStr;
use std::mem;
use std::os::unix::prelude::*;

use libc;
use sys::termios::Termios;
use {Errno, Result, Error, fcntl};

/// Representation of a master/slave pty pair
///
/// This is returned by `openpty`
pub struct OpenptyResult {
pub master: RawFd,
pub slave: RawFd,
}

use {Error, fcntl, Result};

/// Representation of the Master device in a master/slave pty pair
///
Expand Down Expand Up @@ -162,3 +175,42 @@ pub fn unlockpt(fd: &PtyMaster) -> Result<()> {

Ok(())
}


/// Create a new pseudoterminal, returning the slave and master file descriptors
/// in `OpenptyResult`
/// (see [openpty](http://man7.org/linux/man-pages/man3/openpty.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 openpty<'a, 'b, T: Into<Option<&'a Winsize>>, U: Into<Option<&'b Termios>>>(winsize: T, termios: U) -> Result<OpenptyResult> {
use std::ptr;

let mut slave: libc::c_int = -1;
let mut master: libc::c_int = -1;
let c_termios = match termios.into() {
Some(termios) => termios as *const Termios,
None => ptr::null() as *const Termios,
};
let c_winsize = match winsize.into() {
Some(ws) => ws as *const Winsize,
None => ptr::null() as *const Winsize,
};
let ret = unsafe {
libc::openpty(
&mut master as *mut libc::c_int,
&mut slave as *mut libc::c_int,
ptr::null_mut(),
c_termios as *mut libc::termios,
c_winsize as *mut Winsize)
};

Errno::result(ret)?;

Ok(OpenptyResult {
master: master,
slave: slave,
})
}
12 changes: 5 additions & 7 deletions test/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,18 @@ extern crate nix_test as nixtest;

mod sys;
mod test_fcntl;
#[cfg(any(target_os = "linux"))]
mod test_mq;
mod test_net;
mod test_nix_path;
#[cfg(any(target_os = "linux", target_os = "macos"))]
mod test_poll;
mod test_pty;
#[cfg(any(target_os = "linux", target_os = "android"))]
mod test_sendfile;
mod test_stat;
mod test_unistd;

#[cfg(any(target_os = "linux"))]
mod test_mq;

#[cfg(any(target_os = "linux", target_os = "macos"))]
mod test_poll;
mod test_pty;

use nixtest::assert_size_of;

#[test]
Expand Down
82 changes: 82 additions & 0 deletions test/test_pty.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::path::Path;
use std::os::unix::prelude::*;

use nix::fcntl::{O_RDWR, open};
use nix::pty::*;
use nix::sys::stat;
use nix::sys::termios::*;
use nix::unistd::{read, write, close};

/// Test equivalence of `ptsname` and `ptsname_r`
#[test]
Expand Down Expand Up @@ -90,3 +93,82 @@ fn test_open_ptty_pair() {
let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty()).unwrap();
assert!(slave_fd > 0);
}

#[test]
fn test_openpty() {
let pty = openpty(None, None).unwrap();
assert!(pty.master > 0);
assert!(pty.slave > 0);

// Writing to one should be readable on the other one
let string = "foofoofoo\n";
let mut buf = [0u8; 16];
write(pty.master, string.as_bytes()).unwrap();
let len = read(pty.slave, &mut buf).unwrap();

assert_eq!(len, string.len());
assert_eq!(&buf[0..len], string.as_bytes());

// Read the echo as well
let echoed_string = "foofoofoo\r\n";
let len = read(pty.master, &mut buf).unwrap();
assert_eq!(len, echoed_string.len());
assert_eq!(&buf[0..len], echoed_string.as_bytes());

let string2 = "barbarbarbar\n";
let echoed_string2 = "barbarbarbar\r\n";
write(pty.slave, string2.as_bytes()).unwrap();
let len = read(pty.master, &mut buf).unwrap();

assert_eq!(len, echoed_string2.len());
assert_eq!(&buf[0..len], echoed_string2.as_bytes());

close(pty.master).unwrap();
close(pty.slave).unwrap();
}

#[test]
fn test_openpty_with_termios() {
// Open one pty to get attributes for the second one
let mut termios = {
let pty = openpty(None, None).unwrap();
assert!(pty.master > 0);
assert!(pty.slave > 0);
let termios = tcgetattr(pty.master).unwrap();
close(pty.master).unwrap();
close(pty.slave).unwrap();
termios
};
termios.c_oflag &= !ONLCR;

let pty = openpty(None, &termios).unwrap();
// Must be valid file descriptors
assert!(pty.master > 0);
assert!(pty.slave > 0);

// Writing to one should be readable on the other one
let string = "foofoofoo\n";
let mut buf = [0u8; 16];
write(pty.master, string.as_bytes()).unwrap();
let len = read(pty.slave, &mut buf).unwrap();

assert_eq!(len, string.len());
assert_eq!(&buf[0..len], string.as_bytes());

// read the echo as well
let echoed_string = "foofoofoo\n";
let len = read(pty.master, &mut buf).unwrap();
assert_eq!(len, echoed_string.len());
assert_eq!(&buf[0..len], echoed_string.as_bytes());

let string2 = "barbarbarbar\n";
let echoed_string2 = "barbarbarbar\n";
write(pty.slave, string2.as_bytes()).unwrap();
let len = read(pty.master, &mut buf).unwrap();

assert_eq!(len, echoed_string2.len());
assert_eq!(&buf[0..len], echoed_string2.as_bytes());

close(pty.master).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you normally want to close the slave first? Is there anywhere that suggest that closing the master will close the slave as well at the OS-level?

Copy link
Contributor Author

@cactorium cactorium May 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure; the best documentation I could find seemed to imply either way was okay (although nothing implies that closing either end will close the other one):

If fildes refers to the master side of a pseudo-terminal, and this is the last close, a SIGHUP signal shall be sent to the controlling process, if any, for which the slave side of the pseudo-terminal is the controlling terminal. It is unspecified whether closing the master side of the pseudo-terminal flushes all queued input and output.

If fildes refers to the slave side of a STREAMS-based pseudo-terminal, a zero-length message may be sent to the master.

close(pty.slave).unwrap();
}