Skip to content

Commit 96a7553

Browse files
committed
Add various pty functions
* grantpt * ptsname/ptsname_r * posix_openpt * unlockpt
1 parent d810c10 commit 96a7553

File tree

5 files changed

+174
-0
lines changed

5 files changed

+174
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1010
([#582](https://github.com/nix-rust/nix/pull/582)
1111
- Added `nix::unistd::{openat, fstatat, readlink, readlinkat}`
1212
([#551](https://github.com/nix-rust/nix/pull/551))
13+
- Added `nix::pty::{grantpt, posix_openpt, ptsname/ptsname_r, unlockpt}`
14+
([#556](https://github.com/nix-rust/nix/pull/556)
1315

1416
### Changed
1517
- Marked `sys::mman::{ mmap, munmap, madvise, munlock, msync }` as unsafe.

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub mod mount;
4444
#[cfg(target_os = "linux")]
4545
pub mod mqueue;
4646

47+
pub mod pty;
48+
4749
#[cfg(any(target_os = "linux", target_os = "macos"))]
4850
pub mod poll;
4951

src/pty.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! Create master and slave virtual pseudo-terminals (PTTYs)
2+
3+
use std::ffi::CStr;
4+
use std::os::unix::io::RawFd;
5+
6+
use libc;
7+
8+
use {Error, fcntl, Result};
9+
10+
/// Grant access to a slave pseudoterminal (see
11+
/// [grantpt(3)](http://man7.org/linux/man-pages/man3/grantpt.3.html))
12+
///
13+
/// `grantpt()` changezs the mode and owner of the slave pseudoterminal device corresponding to the
14+
/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave.
15+
#[inline]
16+
pub fn grantpt(fd: RawFd) -> Result<()> {
17+
if unsafe { libc::grantpt(fd) } < 0 {
18+
return Err(Error::last().into());
19+
}
20+
21+
Ok(())
22+
}
23+
24+
/// Open a pseudoterminal device (see
25+
/// [posix_openpt(3)](http://man7.org/linux/man-pages/man3/posix_openpt.3.html))
26+
///
27+
/// `posix_openpt()` returns a file descriptor to an existing unused pseuterminal master device.
28+
///
29+
/// # Examples
30+
///
31+
/// A common use case with this function is to open both a master and slave PTTY pair. This can be
32+
/// done as follows:
33+
///
34+
/// ```
35+
/// use std::path::Path;
36+
/// use nix::fcntl::{O_RDWR, open};
37+
/// use nix::pty::*;
38+
/// use nix::sys::stat;
39+
///
40+
/// # #[allow(dead_code)]
41+
/// # fn run() -> nix::Result<()> {
42+
/// // Open a new PTTY master
43+
/// let master_fd = posix_openpt(O_RDWR)?;
44+
///
45+
/// // Allow a slave to be generated for it
46+
/// grantpt(master_fd)?;
47+
/// unlockpt(master_fd)?;
48+
///
49+
/// // Get the name of the slave
50+
/// let slave_name = ptsname(master_fd)?;
51+
///
52+
/// // Try to open the slave
53+
/// # #[allow(unused_variables)]
54+
/// let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty())?;
55+
/// # Ok(())
56+
/// # }
57+
/// ```
58+
#[inline]
59+
pub fn posix_openpt(flags: fcntl::OFlag) -> Result<RawFd> {
60+
let fd = unsafe {
61+
libc::posix_openpt(flags.bits())
62+
};
63+
64+
if fd < 0 {
65+
return Err(Error::last().into());
66+
}
67+
68+
Ok(fd)
69+
}
70+
71+
/// Get the name of the slave pseudoterminal (see
72+
/// [ptsname(3)](http://man7.org/linux/man-pages/man3/ptsname.3.html))
73+
///
74+
/// `ptsname()` returns the name of the slave pseudoterminal device corresponding to the master
75+
/// referred to by `fd`. Note that this function is *not* threadsafe. For that see `ptsname_r()`.
76+
///
77+
/// This value is useful for opening the slave ptty once the master has already been opened with
78+
/// `posix_openpt()`.
79+
#[inline]
80+
pub fn ptsname(fd: RawFd) -> Result<String> {
81+
let name_ptr = unsafe { libc::ptsname(fd) };
82+
if name_ptr.is_null() {
83+
return Err(Error::last().into());
84+
}
85+
86+
let name = unsafe {
87+
CStr::from_ptr(name_ptr)
88+
};
89+
Ok(name.to_string_lossy().into_owned())
90+
}
91+
92+
/// Get the name of the slave pseudoterminal (see
93+
/// [ptsname(3)](http://man7.org/linux/man-pages/man3/ptsname.3.html))
94+
///
95+
/// `ptsname_r()` returns the name of the slave pseudoterminal device corresponding to the master
96+
/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the
97+
/// POSIX standard and is instead a Linux-specific extension.
98+
///
99+
/// This value is useful for opening the slave ptty once the master has already been opened with
100+
/// `posix_openpt()`.
101+
#[cfg(any(target_os = "android", target_os = "linux"))]
102+
#[inline]
103+
pub fn ptsname_r(fd: RawFd) -> Result<String> {
104+
let mut name_buf = [0 as libc::c_char; 64];
105+
if unsafe { libc::ptsname_r(fd, name_buf.as_mut_ptr(), name_buf.len()) } != 0 {
106+
return Err(Error::last().into());
107+
}
108+
109+
let name = unsafe {
110+
CStr::from_ptr(name_buf.as_ptr())
111+
};
112+
Ok(name.to_string_lossy().into_owned())
113+
}
114+
115+
/// Unlock a pseudoterminal master/slave pseudoterminal pair (see
116+
/// [unlockpt(3)](http://man7.org/linux/man-pages/man3/unlockpt.3.html))
117+
///
118+
/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal
119+
/// referred to by `fd`. This must be called before trying to open the slave side of a
120+
/// pseuoterminal.
121+
#[inline]
122+
pub fn unlockpt(fd: RawFd) -> Result<()> {
123+
if unsafe { libc::unlockpt(fd) } < 0 {
124+
return Err(Error::last().into());
125+
}
126+
127+
Ok(())
128+
}

test/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod test_mq;
2323

2424
#[cfg(any(target_os = "linux", target_os = "macos"))]
2525
mod test_poll;
26+
mod test_pty;
2627

2728
use nixtest::assert_size_of;
2829

test/test_pty.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::path::Path;
2+
use nix::fcntl::{O_RDWR, open};
3+
use nix::pty::*;
4+
use nix::sys::stat;
5+
6+
/// Test equivalence of `ptsname` and `ptsname_r`
7+
#[test]
8+
#[cfg(any(target_os = "android", target_os = "linux"))]
9+
fn test_ptsname() {
10+
// Open a new PTTY master
11+
let master_fd = posix_openpt(O_RDWR).unwrap();
12+
assert!(master_fd > 0);
13+
14+
// Get the name of the slave
15+
let slave_name = ptsname(master_fd).unwrap();
16+
let slave_name_r = ptsname_r(master_fd).unwrap();
17+
assert_eq!(slave_name, slave_name_r);
18+
}
19+
20+
/// Test opening a master/slave PTTY pair
21+
///
22+
/// This is a single larger test because much of these functions aren't useful by themselves. So for
23+
/// this test we perform the basic act of getting a file handle for a connect master/slave PTTY
24+
/// pair.
25+
#[test]
26+
fn test_open_ptty_pair() {
27+
// Open a new PTTY master
28+
let master_fd = posix_openpt(O_RDWR).unwrap();
29+
assert!(master_fd > 0);
30+
31+
// Allow a slave to be generated for it
32+
grantpt(master_fd).unwrap();
33+
unlockpt(master_fd).unwrap();
34+
35+
// Get the name of the slave
36+
let slave_name = ptsname(master_fd).unwrap();
37+
38+
// Open the slave device
39+
let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty()).unwrap();
40+
assert!(slave_fd > 0);
41+
}

0 commit comments

Comments
 (0)