Skip to content

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

Draft
gdw2vs wants to merge 2 commits into
bytecodealliance:mainfrom
gdw2vs:macos-ttyname-fcntl-getpath
Draft

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

Conversation

@gdw2vs

@gdw2vs gdw2vs commented Apr 9, 2026

Copy link
Copy Markdown

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

xtqqczze commented Apr 9, 2026

Copy link
Copy Markdown
Contributor
@xtqqczze

Copy link
Copy Markdown
Contributor

@gdw2vs I guess you should convert this to draft until #1606 is merged.

@gdw2vs gdw2vs marked this pull request as draft April 20, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants