From b3bc9c0ec12dd503def43c1a075dfb85a67df756 Mon Sep 17 00:00:00 2001 From: Gregory Warner Date: Wed, 8 Apr 2026 19:49:05 -0600 Subject: [PATCH 1/2] 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/ 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. --- src/backend/libc/termios/syscalls.rs | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/backend/libc/termios/syscalls.rs b/src/backend/libc/termios/syscalls.rs index dbe4a6e03..4f47cf9b9 100644 --- a/src/backend/libc/termios/syscalls.rs +++ b/src/backend/libc/termios/syscalls.rs @@ -521,6 +521,38 @@ pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool { #[cfg(feature = "alloc")] #[cfg(not(any(target_os = "fuchsia", target_os = "wasi")))] pub(crate) fn ttyname(dirfd: BorrowedFd<'_>, buf: &mut [MaybeUninit]) -> io::Result { + // On Apple platforms, use `fcntl(F_GETPATH)` instead of `ttyname_r`. + // + // macOS's `ttyname_r` works by walking `/dev`, calling `stat` on each + // entry and comparing device and inode numbers until it finds a match. + // This directory scan can take several seconds on a typical macOS system. + // + // `fcntl(F_GETPATH)` is a Darwin-specific API that asks the kernel to + // fill a buffer with the path for any open file descriptor. It is a + // single kernel call and is dramatically faster. + // + // The Linux `linux_raw` backend uses an analogous approach, reading the + // path from `/proc/self/fd/` to avoid `ttyname_r` entirely. + #[cfg(apple)] + unsafe { + // `F_GETPATH` works on any open fd, not just ttys. Check `isatty` + // first so we return `ENOTTY` for non-terminal fds, matching the + // behavior of POSIX `ttyname`. + if !isatty(dirfd) { + return Err(io::Errno::NOTTY); + } + + // From the macOS `fcntl(2)` man page: `F_GETPATH` requires a buffer + // of at least `MAXPATHLEN` bytes. `PATH_MAX` equals `MAXPATHLEN`. + if buf.len() < c::PATH_MAX as usize { + return Err(io::Errno::RANGE); + } + + ret(c::fcntl(borrowed_fd(dirfd), c::F_GETPATH, buf.as_mut_ptr()))?; + Ok(CStr::from_ptr(buf.as_ptr().cast()).to_bytes().len()) + } + + #[cfg(not(apple))] unsafe { // `ttyname_r` returns its error status rather than using `errno`. match c::ttyname_r(borrowed_fd(dirfd), buf.as_mut_ptr().cast(), buf.len()) { From 28485e216b88274e6e922f5f9713b0a26cbaaa0f Mon Sep 17 00:00:00 2001 From: Gregory Warner Date: Wed, 8 Apr 2026 21:37:45 -0600 Subject: [PATCH 2/2] ci: pin libc to 0.2.182 for Rust 1.63 MSRV compatibility 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. --- .github/workflows/main.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0955f7683..f86810394 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,12 +76,14 @@ jobs: cargo update --package=futures-executor --precise=0.3.31 cargo update --package=futures-util --precise=0.3.31 cargo update --package=futures-channel --precise=0.3.31 - cargo update --package=futures-core --precise=0.3.31 - cargo update --package=futures-io --precise=0.3.31 - cargo update --package=futures-sink --precise=0.3.31 - cargo update --package=futures-task --precise=0.3.31 + cargo update --package=futures-core --precise=0.3.31 + cargo update --package=futures-io --precise=0.3.31 + cargo update --package=futures-sink --precise=0.3.31 + cargo update --package=futures-task --precise=0.3.31 + cargo update --package=libc --precise=0.2.182 - run: > + rustup target add x86_64-unknown-linux-musl x86_64-unknown-linux-gnux32 @@ -613,12 +615,14 @@ jobs: cargo update --package=futures-executor --precise=0.3.31 cargo update --package=futures-util --precise=0.3.31 cargo update --package=futures-channel --precise=0.3.31 - cargo update --package=futures-core --precise=0.3.31 - cargo update --package=futures-io --precise=0.3.31 - cargo update --package=futures-sink --precise=0.3.31 - cargo update --package=futures-task --precise=0.3.31 + cargo update --package=futures-core --precise=0.3.31 + cargo update --package=futures-io --precise=0.3.31 + cargo update --package=futures-sink --precise=0.3.31 + cargo update --package=futures-task --precise=0.3.31 + cargo update --package=libc --precise=0.2.182 - run: | + cargo test --verbose --features=all-apis --release --workspace -- --nocapture env: RUST_BACKTRACE: full