summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2017-05-17 03:48:24 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2017-05-17 03:48:24 +0000
commitba5c8371ab097f269bc29c302ebc655a1a59f12a (patch)
tree938a5eea5ccadc70509a856d718adb800b386a03
parent8498bd97e0cd6d6cd84ff472a28c6f55850c61e1 (diff)
parent4e97520799ca50e8dde47bfe3d098b992aafe1d8 (diff)
downloadnix-ba5c8371ab097f269bc29c302ebc655a1a59f12a.zip
Merge #556
556: Add various pty functions r=asomers * grantpt * posix_openpt * ptsname/ptsname_r * unlockpt I've refactored my code to use these functions and they work correctly at least in the non-error case. Only issue is what to do for failure in `ptsname` when converting strings. This should effectively never happen. I think maybe adding an `Unknown` to the `Error` type and returning that might be useful.
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/lib.rs15
-rw-r--r--src/pty.rs164
-rw-r--r--test/test.rs1
-rw-r--r--test/test_pty.rs92
5 files changed, 273 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a4310d47..7d59d217 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 ecbf139d..42f23334 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;
@@ -92,6 +94,9 @@ pub type Result<T> = result::Result<T, Error>;
pub enum Error {
Sys(errno::Errno),
InvalidPath,
+ /// The operation involved a conversion to Rust's native String type, which failed because the
+ /// string did not contain all valid UTF-8.
+ InvalidUtf8,
}
impl Error {
@@ -114,8 +119,9 @@ impl Error {
/// Get the errno associated with this error
pub fn errno(&self) -> errno::Errno {
match *self {
- Error::Sys(errno) => errno,
Error::InvalidPath => errno::Errno::EINVAL,
+ Error::InvalidUtf8 => errno::Errno::UnknownErrno,
+ Error::Sys(errno) => errno,
}
}
}
@@ -124,10 +130,15 @@ impl From<errno::Errno> for Error {
fn from(errno: errno::Errno) -> Error { Error::from_errno(errno) }
}
+impl From<std::string::FromUtf8Error> for Error {
+ fn from(_: std::string::FromUtf8Error) -> Error { Error::InvalidUtf8 }
+}
+
impl error::Error for Error {
fn description(&self) -> &str {
match self {
&Error::InvalidPath => "Invalid path",
+ &Error::InvalidUtf8 => "Invalid UTF-8 string",
&Error::Sys(ref errno) => errno.desc(),
}
}
@@ -137,6 +148,7 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::InvalidPath => write!(f, "Invalid path"),
+ &Error::InvalidUtf8 => write!(f, "Invalid UTF-8 string"),
&Error::Sys(errno) => write!(f, "{:?}: {}", errno, errno.desc()),
}
}
@@ -146,6 +158,7 @@ impl From<Error> for io::Error {
fn from(err: Error) -> Self {
match err {
Error::InvalidPath => io::Error::new(io::ErrorKind::InvalidInput, err),
+ Error::InvalidUtf8 => io::Error::new(io::ErrorKind::Other, err),
Error::Sys(errno) => io::Error::from_raw_os_error(errno as i32),
}
}
diff --git a/src/pty.rs b/src/pty.rs
new file mode 100644
index 00000000..3e9c44ea
--- /dev/null
+++ b/src/pty.rs
@@ -0,0 +1,164 @@
+//! Create master and slave virtual pseudo-terminals (PTYs)
+
+use std::ffi::CStr;
+use std::mem;
+use std::os::unix::prelude::*;
+
+use libc;
+
+use {Error, fcntl, Result};
+
+/// Representation of the Master device in a master/slave pty pair
+///
+/// While this datatype is a thin wrapper around `RawFd`, it enforces that the available PTY
+/// functions are given the correct file descriptor. Additionally this type implements `Drop`,
+/// so that when it's consumed or goes out of scope, it's automatically cleaned-up.
+#[derive(Debug)]
+pub struct PtyMaster(RawFd);
+
+impl AsRawFd for PtyMaster {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0
+ }
+}
+
+impl IntoRawFd for PtyMaster {
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.0;
+ mem::forget(self);
+ fd
+ }
+}
+
+impl Drop for PtyMaster {
+ fn drop(&mut self) {
+ // Errors when closing are ignored because we don't actually know if the file descriptor
+ // was closed. If we retried, it's possible that descriptor was reallocated in the mean
+ // time and the wrong file descriptor could be closed.
+ let _ = ::unistd::close(self.0);
+ }
+}
+
+/// Grant access to a slave pseudoterminal (see
+/// [grantpt(3)](http://man7.org/linux/man-pages/man3/grantpt.3.html))
+///
+/// `grantpt()` changes 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: &PtyMaster) -> Result<()> {
+ if unsafe { libc::grantpt(fd.as_raw_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 PTY 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 PTY 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<PtyMaster> {
+ let fd = unsafe {
+ libc::posix_openpt(flags.bits())
+ };
+
+ if fd < 0 {
+ return Err(Error::last().into());
+ }
+
+ Ok(PtyMaster(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 pty once the master has already been opened with
+/// `posix_openpt()`.
+#[inline]
+pub fn ptsname(fd: &PtyMaster) -> Result<String> {
+ let name_ptr = unsafe { libc::ptsname(fd.as_raw_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: &PtyMaster) -> Result<String> {
+ let mut name_buf = vec![0u8; 64];
+ let name_buf_ptr = name_buf.as_mut_ptr() as *mut libc::c_char;
+ if unsafe { libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, name_buf.capacity()) } != 0 {
+ return Err(Error::last().into());
+ }
+
+ // Find the first null-character terminating this string. This is guaranteed to succeed if the
+ // return value of `libc::ptsname_r` is 0.
+ let null_index = name_buf.iter().position(|c| *c == b'\0').unwrap();
+ name_buf.truncate(null_index);
+
+ let name = String::from_utf8(name_buf)?;
+ Ok(name)
+}
+
+/// 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: &PtyMaster) -> Result<()> {
+ if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 {
+ return Err(Error::last().into());
+ }
+
+ Ok(())
+}
diff --git a/test/test.rs b/test/test.rs
index 1357642e..1f87171d 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 00000000..61780421
--- /dev/null
+++ b/test/test_pty.rs
@@ -0,0 +1,92 @@
+use std::path::Path;
+use std::os::unix::prelude::*;
+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_equivalence() {
+ // Open a new PTTY master
+ let master_fd = posix_openpt(O_RDWR).unwrap();
+ assert!(master_fd.as_raw_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 data copying of `ptsname`
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_copy() {
+ // Open a new PTTY master
+ let master_fd = posix_openpt(O_RDWR).unwrap();
+ assert!(master_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = ptsname(&master_fd).unwrap();
+ let slave_name2 = ptsname(&master_fd).unwrap();
+ assert!(slave_name1 == slave_name2);
+ // Also make sure that the string was actually copied and they point to different parts of
+ // memory.
+ assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
+}
+
+/// Test data copying of `ptsname_r`
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_r_copy() {
+ // Open a new PTTY master
+ let master_fd = posix_openpt(O_RDWR).unwrap();
+ assert!(master_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = ptsname_r(&master_fd).unwrap();
+ let slave_name2 = ptsname_r(&master_fd).unwrap();
+ assert!(slave_name1 == slave_name2);
+ assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
+}
+
+/// Test that `ptsname` returns different names for different devices
+#[test]
+#[cfg(any(target_os = "android", target_os = "linux"))]
+fn test_ptsname_unique() {
+ // Open a new PTTY master
+ let master1_fd = posix_openpt(O_RDWR).unwrap();
+ assert!(master1_fd.as_raw_fd() > 0);
+
+ // Open a second PTTY master
+ let master2_fd = posix_openpt(O_RDWR).unwrap();
+ assert!(master2_fd.as_raw_fd() > 0);
+
+ // Get the name of the slave
+ let slave_name1 = ptsname(&master1_fd).unwrap();
+ let slave_name2 = ptsname(&master2_fd).unwrap();
+ assert!(slave_name1 != slave_name2);
+}
+
+/// 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.as_raw_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);
+}