Skip to content

Commit f192405

Browse files
committed
Provide and emulate ptsname_r on macOS and iOS by using the TIOCPTYGNAME syscall.
This provides a thread-safe alternative to ptsname() and compatibility for existing projects using ptsname_r() on macOS.
1 parent b76cbee commit f192405

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

src/pty.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,41 @@ pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
169169
Ok(name)
170170
}
171171

172+
/// (emulated on macOS) Get the name of the slave pseudoterminal (see
173+
/// [ptsname(3)](http://man7.org/linux/man-pages/man3/ptsname.3.html))
174+
///
175+
/// returns the name of the slave pseudoterminal device corresponding to the master
176+
/// referred to by `fd`. This is the threadsafe version of `ptsname()`, but it is not part of the
177+
/// POSIX standard and is instead a Linux-specific extension.
178+
///
179+
/// This value is useful for opening the slave ptty once the master has already been opened with
180+
/// `posix_openpt()`.
181+
///
182+
/// As `ptsname_r()` is Linux-specific, this implementation emulates `ptsname_r()` through
183+
/// the `TIOCPTYGNAME` syscall on macOS.
184+
#[cfg(any(target_os = "macos", target_os = "ios"))]
185+
#[inline]
186+
pub fn ptsname_r(fd: &PtyMaster) -> Result<String> {
187+
// This is based on
188+
// https://blog.tarq.io/ptsname-on-osx-with-rust/
189+
// and its derivative
190+
// https://github.com/philippkeller/rexpect/blob/a71dd02/src/process.rs#L67
191+
use libc::{ioctl, TIOCPTYGNAME};
192+
193+
// the buffer size on OSX is 128, defined by sys/ttycom.h
194+
let buf: [i8; 128] = [0; 128];
195+
196+
unsafe {
197+
match ioctl(fd.as_raw_fd(), TIOCPTYGNAME as u64, &buf) {
198+
0 => {
199+
let res = CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned();
200+
Ok(res)
201+
}
202+
_ => Err(Error::last()),
203+
}
204+
}
205+
}
206+
172207
/// Unlock a pseudoterminal master/slave pseudoterminal pair (see
173208
/// [unlockpt(3)](http://man7.org/linux/man-pages/man3/unlockpt.3.html))
174209
///
@@ -187,7 +222,7 @@ pub fn unlockpt(fd: &PtyMaster) -> Result<()> {
187222

188223
/// Create a new pseudoterminal, returning the slave and master file descriptors
189224
/// in `OpenptyResult`
190-
/// (see [openpty](http://man7.org/linux/man-pages/man3/openpty.3.html)).
225+
/// (see [openpty](http://man7.org/linux/man-pages/man3/openpty.3.html)).
191226
///
192227
/// If `winsize` is not `None`, the window size of the slave will be set to
193228
/// the values in `winsize`. If `termios` is not `None`, the pseudoterminal's

test/test_pty.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ fn test_explicit_close() {
2525

2626
/// Test equivalence of `ptsname` and `ptsname_r`
2727
#[test]
28-
#[cfg(any(target_os = "android", target_os = "linux"))]
28+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "ios"))]
2929
fn test_ptsname_equivalence() {
3030
#[allow(unused_variables)]
3131
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
@@ -43,7 +43,7 @@ fn test_ptsname_equivalence() {
4343
/// Test data copying of `ptsname`
4444
// TODO need to run in a subprocess, since ptsname is non-reentrant
4545
#[test]
46-
#[cfg(any(target_os = "android", target_os = "linux"))]
46+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "ios"))]
4747
fn test_ptsname_copy() {
4848
#[allow(unused_variables)]
4949
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");
@@ -63,7 +63,7 @@ fn test_ptsname_copy() {
6363

6464
/// Test data copying of `ptsname_r`
6565
#[test]
66-
#[cfg(any(target_os = "android", target_os = "linux"))]
66+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "ios"))]
6767
fn test_ptsname_r_copy() {
6868
// Open a new PTTY master
6969
let master_fd = posix_openpt(O_RDWR).unwrap();
@@ -78,7 +78,7 @@ fn test_ptsname_r_copy() {
7878

7979
/// Test that `ptsname` returns different names for different devices
8080
#[test]
81-
#[cfg(any(target_os = "android", target_os = "linux"))]
81+
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "ios"))]
8282
fn test_ptsname_unique() {
8383
#[allow(unused_variables)]
8484
let m = ::PTSNAME_MTX.lock().expect("Mutex got poisoned by another test");

0 commit comments

Comments
 (0)