Skip to content

Commit 3b71257

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

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-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: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 = "linux",
102+
target_os = "android",
103+
target_os = "emscripten",
104+
target_os = "fuchsia"))]
105+
#[inline]
106+
pub fn ptsname_r(fd: RawFd) -> Result<String> {
107+
let mut name_buf = [0 as libc::c_char; 64];
108+
if unsafe { libc::ptsname_r(fd, name_buf.as_mut_ptr(), name_buf.len()) } != 0 {
109+
return Err(Error::last().into());
110+
}
111+
112+
let name = unsafe {
113+
CStr::from_ptr(name_buf.as_ptr())
114+
};
115+
Ok(name.to_string_lossy().into_owned())
116+
}
117+
118+
/// Unlock a pseudoterminal master/slave pseudoterminal pair (see
119+
/// [unlockpt(3)](http://man7.org/linux/man-pages/man3/unlockpt.3.html))
120+
///
121+
/// `unlockpt()` unlocks the slave pseudoterminal device corresponding to the master pseudoterminal
122+
/// referred to by `fd`. This must be called before trying to open the slave side of a
123+
/// pseuoterminal.
124+
#[inline]
125+
pub fn unlockpt(fd: RawFd) -> Result<()> {
126+
if unsafe { libc::unlockpt(fd) } < 0 {
127+
return Err(Error::last().into());
128+
}
129+
130+
Ok(())
131+
}

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

0 commit comments

Comments
 (0)