Skip to content

Commit b3bc9c0

Browse files
committed
termios: use fcntl(F_GETPATH) for ttyname on Apple platforms
macOS's ttyname_r works by walking /dev and calling stat on each entry to find one whose device and inode numbers match the given fd. This directory scan can take several seconds on a typical macOS system, causing significant latency for any Rust program that calls ttyname. On Apple platforms, use fcntl(F_GETPATH) instead. This is a Darwin-specific API that asks the kernel to fill a buffer with the filesystem path for any open file descriptor in a single kernel call, making it dramatically faster than the /dev scan. The linux_raw backend already uses an analogous approach for the same reason, reading the path from /proc/self/fd/<fd> instead of calling ttyname_r. This change brings the libc backend on Apple targets to parity with that design. The existing fs::getpath function in the libc backend already uses fcntl(F_GETPATH) for the same purpose on Apple targets, so this is consistent with established patterns in the codebase.
1 parent c4caf5c commit b3bc9c0

File tree

1 file changed

+32
-0
lines changed

1 file changed

+32
-0
lines changed

src/backend/libc/termios/syscalls.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,38 @@ pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
521521
#[cfg(feature = "alloc")]
522522
#[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))]
523523
pub(crate) fn ttyname(dirfd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
524+
// On Apple platforms, use `fcntl(F_GETPATH)` instead of `ttyname_r`.
525+
//
526+
// macOS's `ttyname_r` works by walking `/dev`, calling `stat` on each
527+
// entry and comparing device and inode numbers until it finds a match.
528+
// This directory scan can take several seconds on a typical macOS system.
529+
//
530+
// `fcntl(F_GETPATH)` is a Darwin-specific API that asks the kernel to
531+
// fill a buffer with the path for any open file descriptor. It is a
532+
// single kernel call and is dramatically faster.
533+
//
534+
// The Linux `linux_raw` backend uses an analogous approach, reading the
535+
// path from `/proc/self/fd/<fd>` to avoid `ttyname_r` entirely.
536+
#[cfg(apple)]
537+
unsafe {
538+
// `F_GETPATH` works on any open fd, not just ttys. Check `isatty`
539+
// first so we return `ENOTTY` for non-terminal fds, matching the
540+
// behavior of POSIX `ttyname`.
541+
if !isatty(dirfd) {
542+
return Err(io::Errno::NOTTY);
543+
}
544+
545+
// From the macOS `fcntl(2)` man page: `F_GETPATH` requires a buffer
546+
// of at least `MAXPATHLEN` bytes. `PATH_MAX` equals `MAXPATHLEN`.
547+
if buf.len() < c::PATH_MAX as usize {
548+
return Err(io::Errno::RANGE);
549+
}
550+
551+
ret(c::fcntl(borrowed_fd(dirfd), c::F_GETPATH, buf.as_mut_ptr()))?;
552+
Ok(CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len())
553+
}
554+
555+
#[cfg(not(apple))]
524556
unsafe {
525557
// `ttyname_r` returns its error status rather than using `errno`.
526558
match c::ttyname_r(borrowed_fd(dirfd), buf.as_mut_ptr().cast(), buf.len()) {

0 commit comments

Comments
 (0)