From 96a755376874eae1bf7b660cf1898579a617f022 Mon Sep 17 00:00:00 2001 From: Bryant Mairs Date: Wed, 15 Mar 2017 10:26:09 -0700 Subject: [PATCH] Add various pty functions * grantpt * ptsname/ptsname_r * posix_openpt * unlockpt --- CHANGELOG.md | 2 + src/lib.rs | 2 + src/pty.rs | 128 +++++++++++++++++++++++++++++++++++++++++++++++ test/test.rs | 1 + test/test_pty.rs | 41 +++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 src/pty.rs create mode 100644 test/test_pty.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a4310d47ff..7d59d217bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#582](https://github.com/nix-rust/nix/pull/582) - Added `nix::unistd::{openat, fstatat, readlink, readlinkat}` ([#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) ### Changed - Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe. diff --git a/src/lib.rs b/src/lib.rs index ecbf139dc6..56ed5c3fa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,8 @@ pub mod mount; #[cfg(target_os = "linux")] pub mod mqueue; +pub mod pty; + #[cfg(any(target_os = "linux", target_os = "macos"))] pub mod poll; diff --git a/src/pty.rs b/src/pty.rs new file mode 100644 index 0000000000..c8f6c48658 --- /dev/null +++ b/src/pty.rs @@ -0,0 +1,128 @@ +//! Create master and slave virtual pseudo-terminals (PTTYs) + +use std::ffi::CStr; +use std::os::unix::io::RawFd; + +use libc; + +use {Error, fcntl, Result}; + +/// Grant access to a slave pseudoterminal (see +/// [grantpt(3)](http://man7.org/linux/man-pages/man3/grantpt.3.html)) +/// +/// `grantpt()` changezs the mode and owner of the slave pseudoterminal device corresponding to the +/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave. +#[inline] +pub fn grantpt(fd: RawFd) -> Result<()> { + if unsafe { libc::grantpt(fd) } < 0 { + return Err(Error::last().into()); + } + + Ok(()) +} + +/// Open a pseudoterminal device (see +/// [posix_openpt(3)](http://man7.org/linux/man-pages/man3/posix_openpt.3.html)) +/// +/// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device. +/// +/// # Examples +/// +/// A common use case with this function is to open both a master and slave PTTY pair. This can be +/// done as follows: +/// +/// ``` +/// use std::path::Path; +/// use nix::fcntl::{O_RDWR, open}; +/// use nix::pty::*; +/// use nix::sys::stat; +/// +/// # #[allow(dead_code)] +/// # fn run() -> nix::Result<()> { +/// // Open a new PTTY master +/// let master_fd = posix_openpt(O_RDWR)?; +/// +/// // Allow a slave to be generated for it +/// grantpt(master_fd)?; +/// unlockpt(master_fd)?; +/// +/// // Get the name of the slave +/// let slave_name = ptsname(master_fd)?; +/// +/// // Try to open the slave +/// # #[allow(unused_variables)] +/// let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty())?; +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn posix_openpt(flags: fcntl::OFlag) -> Result { + let fd = unsafe { + libc::posix_openpt(flags.bits()) + }; + + if fd < 0 { + return Err(Error::last().into()); + } + + Ok(fd) +} + +/// Get the name of the slave pseudoterminal (see +/// [ptsname(3)](http://man7.org/linux/man-pages/man3/ptsname.3.html)) +/// +/// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master +/// referred to by `fd`. Note that this function is *not* threadsafe. For that see `ptsname_r()`. +/// +/// This value is useful for opening the slave ptty once the master has already been opened with +/// `posix_openpt()`. +#[inline] +pub fn ptsname(fd: RawFd) -> Result { + let name_ptr = unsafe { libc::ptsname(fd) }; + if name_ptr.is_null() { + return Err(Error::last().into()); + } + + let name = unsafe { + CStr::from_ptr(name_ptr) + }; + Ok(name.to_string_lossy().into_owned()) +} + +/// Get the name of the slave pseudoterminal (see +/// [ptsname(3)](http://man7.org/linux/man-pages/man3/ptsname.3.html)) +/// +/// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master +/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the +/// POSIX standard and is instead a Linux-specific extension. +/// +/// This value is useful for opening the slave ptty once the master has already been opened with +/// `posix_openpt()`. +#[cfg(any(target_os = "android", target_os = "linux"))] +#[inline] +pub fn ptsname_r(fd: RawFd) -> Result { + let mut name_buf = [0 as libc::c_char; 64]; + if unsafe { libc::ptsname_r(fd, name_buf.as_mut_ptr(), name_buf.len()) } != 0 { + return Err(Error::last().into()); + } + + let name = unsafe { + CStr::from_ptr(name_buf.as_ptr()) + }; + Ok(name.to_string_lossy().into_owned()) +} + +/// Unlock a pseudoterminal master/slave pseudoterminal pair (see +/// [unlockpt(3)](http://man7.org/linux/man-pages/man3/unlockpt.3.html)) +/// +/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal +/// referred to by `fd`. This must be called before trying to open the slave side of a +/// pseuoterminal. +#[inline] +pub fn unlockpt(fd: RawFd) -> Result<()> { + if unsafe { libc::unlockpt(fd) } < 0 { + return Err(Error::last().into()); + } + + Ok(()) +} diff --git a/test/test.rs b/test/test.rs index 1357642ef6..1f87171da5 100644 --- a/test/test.rs +++ b/test/test.rs @@ -23,6 +23,7 @@ mod test_mq; #[cfg(any(target_os = "linux", target_os = "macos"))] mod test_poll; +mod test_pty; use nixtest::assert_size_of; diff --git a/test/test_pty.rs b/test/test_pty.rs new file mode 100644 index 0000000000..3e36622f40 --- /dev/null +++ b/test/test_pty.rs @@ -0,0 +1,41 @@ +use std::path::Path; +use nix::fcntl::{O_RDWR, open}; +use nix::pty::*; +use nix::sys::stat; + +/// Test equivalence of `ptsname` and `ptsname_r` +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_ptsname() { + // Open a new PTTY master + let master_fd = posix_openpt(O_RDWR).unwrap(); + assert!(master_fd > 0); + + // Get the name of the slave + let slave_name = ptsname(master_fd).unwrap(); + let slave_name_r = ptsname_r(master_fd).unwrap(); + assert_eq!(slave_name, slave_name_r); +} + +/// Test opening a master/slave PTTY pair +/// +/// This is a single larger test because much of these functions aren't useful by themselves. So for +/// 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() { + // Open a new PTTY master + let master_fd = posix_openpt(O_RDWR).unwrap(); + assert!(master_fd > 0); + + // Allow a slave to be generated for it + grantpt(master_fd).unwrap(); + unlockpt(master_fd).unwrap(); + + // Get the name of the slave + let slave_name = ptsname(master_fd).unwrap(); + + // Open the slave device + let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty()).unwrap(); + assert!(slave_fd > 0); +}