-
Notifications
You must be signed in to change notification settings - Fork 654
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 various pty functions #556
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,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(()) | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/PTTY/PTY here and below |
||
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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test leaks its file descriptors. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a test that calls
ptsname
on two separate pseudoterminals, and checks that their names are not the same? That would enforce that this function performs a data copy (as I think it probably should).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't you instead want to call
ptsname()
on the same ptty and check that they don't point to the same underlying buffer? I'm unclear what the test you suggested actually tests.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that could do it. I'm really just trying to think up ways to improve the coverage.