-
Notifications
You must be signed in to change notification settings - Fork 667
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* grantpt * ptsname/ptsname_r * posix_openpt * unlockpt
- Loading branch information
Showing
5 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
//! 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 = [0 as libc::c_char; 64]; | ||
if unsafe { libc::ptsname_r(fd.as_raw_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: &PtyMaster) -> Result<()> { | ||
if unsafe { libc::unlockpt(fd.as_raw_fd()) } < 0 { | ||
return Err(Error::last().into()); | ||
} | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |