Skip to content

Commit 34cb198

Browse files
committed
Add various pty functions
* grantpt * ptsname/ptsname_r * posix_openpt * unlockpt
1 parent 8498bd9 commit 34cb198

File tree

5 files changed

+253
-0
lines changed

5 files changed

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

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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use std::path::Path;
2+
use nix::fcntl::{O_RDWR, open};
3+
use nix::pty::*;
4+
use nix::sys::stat;
5+
use nix::unistd::close;
6+
7+
/// Test equivalence of `ptsname` and `ptsname_r`
8+
#[test]
9+
#[cfg(any(target_os = "android", target_os = "linux"))]
10+
fn test_ptsname_equivalence() {
11+
// Open a new PTTY master
12+
let master_fd = posix_openpt(O_RDWR).unwrap();
13+
assert!(master_fd.as_raw_fd() > 0);
14+
15+
// Get the name of the slave
16+
let slave_name = ptsname(&master_fd).unwrap();
17+
let slave_name_r = ptsname_r(&master_fd).unwrap();
18+
assert_eq!(slave_name, slave_name_r);
19+
20+
// And cleanup
21+
assert!(close(master_fd.as_raw_fd()).is_ok());
22+
}
23+
24+
/// Test data copying of `ptsname`
25+
#[test]
26+
#[cfg(any(target_os = "android", target_os = "linux"))]
27+
fn test_ptsname_copy() {
28+
// Open a new PTTY master
29+
let master_fd = posix_openpt(O_RDWR).unwrap();
30+
assert!(master_fd.as_raw_fd() > 0);
31+
32+
// Get the name of the slave
33+
let slave_name1 = ptsname(&master_fd).unwrap();
34+
let slave_name2 = ptsname(&master_fd).unwrap();
35+
assert!(slave_name1 == slave_name2);
36+
assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
37+
38+
// And cleanup
39+
assert!(close(master_fd.as_raw_fd()).is_ok());
40+
}
41+
42+
/// Test data copying of `ptsname_r`
43+
#[test]
44+
#[cfg(any(target_os = "android", target_os = "linux"))]
45+
fn test_ptsname_r_copy() {
46+
// Open a new PTTY master
47+
let master_fd = posix_openpt(O_RDWR).unwrap();
48+
assert!(master_fd.as_raw_fd() > 0);
49+
50+
// Get the name of the slave
51+
let slave_name1 = ptsname_r(&master_fd).unwrap();
52+
let slave_name2 = ptsname_r(&master_fd).unwrap();
53+
assert!(slave_name1 == slave_name2);
54+
assert!(slave_name1.as_ptr() != slave_name2.as_ptr());
55+
56+
// And cleanup
57+
assert!(close(master_fd.as_raw_fd()).is_ok());
58+
}
59+
60+
/// Test that `ptsname` returns different names for different devices
61+
#[test]
62+
#[cfg(any(target_os = "android", target_os = "linux"))]
63+
fn test_ptsname_unique() {
64+
// Open a new PTTY master
65+
let master1_fd = posix_openpt(O_RDWR).unwrap();
66+
assert!(master1_fd.as_raw_fd() > 0);
67+
68+
// Open a second PTTY master
69+
let master2_fd = posix_openpt(O_RDWR).unwrap();
70+
assert!(master2_fd.as_raw_fd() > 0);
71+
72+
// Get the name of the slave
73+
let slave_name1 = ptsname(&master1_fd).unwrap();
74+
let slave_name2 = ptsname(&master2_fd).unwrap();
75+
assert!(slave_name1 != slave_name2);
76+
77+
// And cleanup
78+
assert!(close(master1_fd.as_raw_fd()).is_ok());
79+
assert!(close(master2_fd.as_raw_fd()).is_ok());
80+
}
81+
82+
/// Test opening a master/slave PTTY pair
83+
///
84+
/// This is a single larger test because much of these functions aren't useful by themselves. So for
85+
/// this test we perform the basic act of getting a file handle for a connect master/slave PTTY
86+
/// pair.
87+
#[test]
88+
fn test_open_ptty_pair() {
89+
// Open a new PTTY master
90+
let master_fd = posix_openpt(O_RDWR).unwrap();
91+
assert!(master_fd.as_raw_fd() > 0);
92+
93+
// Allow a slave to be generated for it
94+
grantpt(&master_fd).unwrap();
95+
unlockpt(&master_fd).unwrap();
96+
97+
// Get the name of the slave
98+
let slave_name = ptsname(&master_fd).unwrap();
99+
100+
// Open the slave device
101+
let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty()).unwrap();
102+
assert!(slave_fd > 0);
103+
assert!(close(slave_fd).is_ok());
104+
assert!(close(master_fd.as_raw_fd()).is_ok());
105+
}

0 commit comments

Comments
 (0)