Skip to content

termios: use fcntl(F_GETPATH) for ttyname on Apple platforms#1605

Open
gdw2vs wants to merge 2 commits intobytecodealliance:mainfrom
gdw2vs:macos-ttyname-fcntl-getpath
Open

termios: use fcntl(F_GETPATH) for ttyname on Apple platforms#1605
gdw2vs wants to merge 2 commits intobytecodealliance:mainfrom
gdw2vs:macos-ttyname-fcntl-getpath

Conversation

@gdw2vs
Copy link
Copy Markdown

@gdw2vs gdw2vs commented Apr 9, 2026

Problem

On macOS, rustix::termios::ttyname calls libc::ttyname_r, which works by walking /dev and calling stat on each entry, comparing device and inode numbers until it finds a match. This directory scan can take several seconds on a typical macOS system, causing significant latency for any Rust program that calls ttyname.

This is a known macOS libc behavior. The slowness has been observed and reported in downstream crates (e.g. doy/rbw#11) that were forced to work around it by calling fcntl(F_GETPATH) directly rather than going through rustix::termios::ttyname.

Solution

On Apple platforms, use fcntl(F_GETPATH) instead of ttyname_r. 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 change is in src/backend/libc/termios/syscalls.rs, splitting the ttyname function into two #[cfg]-guarded branches:

  • #[cfg(apple)]: uses fcntl(F_GETPATH), with an explicit isatty check beforehand to preserve POSIX ENOTTY semantics for non-terminal fds
  • #[cfg(not(apple))]: the existing ttyname_r path, unchanged

Precedent in this codebase

This follows the same reasoning as the linux_raw backend, which already avoids ttyname_r entirely by reading the path from /proc/self/fd/<fd>. The fcntl(F_GETPATH) approach on Apple is the direct analogue.

Additionally, fcntl(F_GETPATH) is already used within rustix itself in src/backend/libc/fs/syscalls.rs (fs::getpath), so this is consistent with established patterns in the codebase.

Testing

All existing ttyname tests pass:

  • ttyname::test_ttyname_ok — verifies a tty fd returns a valid /dev/... path
  • ttyname::test_ttyname_not_tty — verifies non-tty fds return ENOTTY
cargo test --features=all-apis

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.
libc 0.2.183+ requires rustc 1.65, which breaks the ubuntu-1.63 test
jobs. Pin libc to 0.2.182 in the 1.63 compatibility cargo update
blocks, matching the pattern already used for other dependencies.
@xtqqczze
Copy link
Copy Markdown
Contributor

xtqqczze commented Apr 9, 2026

CI / Check (1.63) (pull_request) failure tracked by #1607

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants