Skip to content

Commit

Permalink
Add a rustix::pty module. (#673)
Browse files Browse the repository at this point in the history
Add a `rustix::pty` module, providing functions that wrap
`posix_openpt`, `grantpt`, `unlockpt`, `ptsname`, and so on.
  • Loading branch information
sunfishcode committed May 20, 2023
1 parent 94edb27 commit c2699e3
Show file tree
Hide file tree
Showing 17 changed files with 482 additions and 5 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ param = ["fs"]
# Enable this to enable `rustix::io::proc_self_*` (on Linux) and `ttyname`.
procfs = ["once_cell", "itoa", "fs"]

# Enable `rustix::pty::*`.
pty = ["itoa", "fs"]

# Enable `rustix::termios::*`.
termios = []

Expand All @@ -188,6 +191,7 @@ all-apis = [
"param",
"process",
"procfs",
"pty",
"rand",
"runtime",
"termios",
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ by default. The rest of the API is conditional with cargo feature flags:
| `net` | [`rustix::net`] and [`rustix::path`]—Network-related operations.
| `param` | [`rustix::param`]—Process parameters.
| `process` | [`rustix::process`]—Process-associated operations.
| `pty` | [`rustix::pty`]—Pseduoterminal operations.
| `rand` | [`rustix::rand`]—Random-related operations.
| `termios` | [`rustix::termios`]—Terminal I/O stream operations.
| `thread` | [`rustix::thread`]—Thread-associated operations.
Expand All @@ -76,6 +77,7 @@ by default. The rest of the API is conditional with cargo feature flags:
[`rustix::net`]: https://docs.rs/rustix/*/rustix/net/index.html
[`rustix::param`]: https://docs.rs/rustix/*/rustix/param/index.html
[`rustix::process`]: https://docs.rs/rustix/*/rustix/process/index.html
[`rustix::pty`]: https://docs.rs/rustix/*/rustix/pty/index.html
[`rustix::rand`]: https://docs.rs/rustix/*/rustix/rand/index.html
[`rustix::termios`]: https://docs.rs/rustix/*/rustix/termios/index.html
[`rustix::thread`]: https://docs.rs/rustix/*/rustix/thread/index.html
Expand Down
4 changes: 4 additions & 0 deletions src/backend/libc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ pub(crate) mod param;
#[cfg(not(windows))]
pub(crate) mod process;
#[cfg(not(windows))]
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "pty")]
pub(crate) mod pty;
#[cfg(not(windows))]
#[cfg(feature = "rand")]
pub(crate) mod rand;
#[cfg(not(windows))]
Expand Down
14 changes: 9 additions & 5 deletions src/backend/libc/process/syscalls.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
//! libc syscalls supporting `rustix::process`.

use super::super::c;
#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
use super::super::conv::borrowed_fd;
use super::super::conv::{c_str, ret, ret_c_int, ret_discarded_char_ptr};
#[cfg(not(target_os = "wasi"))]
use super::super::conv::{ret_infallible, ret_pid_t, ret_usize};
use super::super::conv::{borrowed_fd, ret_infallible, ret_pid_t, ret_usize};
use super::super::conv::{c_str, ret, ret_c_int, ret_discarded_char_ptr};
#[cfg(any(target_os = "android", target_os = "linux"))]
use super::super::conv::{syscall_ret, syscall_ret_u32};
#[cfg(any(
Expand All @@ -15,7 +13,7 @@ use super::super::conv::{syscall_ret, syscall_ret_u32};
target_os = "linux",
))]
use super::types::RawCpuSet;
#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
#[cfg(not(target_os = "wasi"))]
use crate::fd::BorrowedFd;
#[cfg(target_os = "linux")]
use crate::fd::{AsRawFd, OwnedFd};
Expand Down Expand Up @@ -648,3 +646,9 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
))
}
}

#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
#[inline]
pub(crate) fn ioctl_tiocsctty(fd: BorrowedFd<'_>) -> io::Result<()> {
unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCSCTTY as _, &0_u32)) }
}
1 change: 1 addition & 0 deletions src/backend/libc/pty/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod syscalls;
87 changes: 87 additions & 0 deletions src/backend/libc/pty/syscalls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! libc syscalls supporting `rustix::pty`.

use super::super::c;
use super::super::conv::{borrowed_fd, ret};
use crate::fd::BorrowedFd;
use crate::io;
#[cfg(not(target_os = "android"))]
use {super::super::conv::ret_owned_fd, crate::fd::OwnedFd, crate::pty::OpenptFlags};
#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
use {
crate::ffi::{CStr, CString},
crate::path::SMALL_PATH_BUFFER_SIZE,
alloc::borrow::ToOwned,
alloc::vec::Vec,
};

#[cfg(not(any(target_os = "android", target_os = "linux")))]
#[inline]
pub(crate) fn openpt(flags: OpenptFlags) -> io::Result<OwnedFd> {
unsafe { ret_owned_fd(c::posix_openpt(flags.bits() as _)) }
}

#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
#[inline]
pub(crate) fn ptsname(fd: BorrowedFd, mut buffer: Vec<u8>) -> io::Result<CString> {
// This code would benefit from having a better way to read into
// uninitialized memory, but that requires `unsafe`.
buffer.clear();
buffer.reserve(SMALL_PATH_BUFFER_SIZE);
buffer.resize(buffer.capacity(), 0_u8);

loop {
// On platforms with `ptsname_r`, use it.
#[cfg(any(target_os = "freebsd", linux_like, target_os = "fuchsia"))]
let r =
unsafe { libc::ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len()) };

// MacOS 10.13.4 has `ptsname_r`; use it if we have it, otherwise fall
// back to calling the underlying ioctl directly.
#[cfg(apple)]
let r = unsafe {
weak! { fn ptsname_r(c::c_int, *mut c::c_char, c::size_t) -> c::c_int }

if let Some(libc_ptsname_r) = ptsname_r.get() {
libc_ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len())
} else {
// The size declared in the `TIOCPTYGNAME` macro in sys/ttycom.h is 128.
let mut name: [u8; 128] = [0_u8; 128];
match libc::ioctl(borrowed_fd(fd), libc::TIOCPTYGNAME as u64, &mut name) {
0 => {
let len = CStr::from_ptr(name.as_ptr().cast()).to_bytes().len();
std::ptr::copy_nonoverlapping(name.as_ptr(), buffer.as_mut_ptr(), len + 1);
0
}
_ => libc_errno::errno().0,
}
}
};

if r == 0 {
return Ok(unsafe { CStr::from_ptr(buffer.as_ptr().cast()).to_owned() });
}
if r != libc::ERANGE {
return Err(io::Errno::from_raw_os_error(r));
}

buffer.reserve(1); // use `Vec` reallocation strategy to grow capacity exponentially
buffer.resize(buffer.capacity(), 0_u8);
}
}

#[inline]
pub(crate) fn unlockpt(fd: BorrowedFd) -> io::Result<()> {
unsafe { ret(c::unlockpt(borrowed_fd(fd))) }
}

#[cfg(not(any(target_os = "android", target_os = "linux")))]
#[inline]
pub(crate) fn grantpt(fd: BorrowedFd) -> io::Result<()> {
unsafe { ret(c::grantpt(borrowed_fd(fd))) }
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn ioctl_tiocgptpeer(fd: BorrowedFd, flags: OpenptFlags) -> io::Result<OwnedFd> {
unsafe { ret_owned_fd(c::ioctl(borrowed_fd(fd), c::TIOCGPTPEER, flags.bits())) }
}
1 change: 1 addition & 0 deletions src/backend/linux_raw/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ pub(crate) use linux_raw_sys::general::{
SO_SNDTIMEO_NEW, SO_SNDTIMEO_OLD, SO_TYPE, TCP_NODELAY,
};
pub(crate) use linux_raw_sys::general::{NFS_SUPER_MAGIC, PROC_SUPER_MAGIC, UTIME_NOW, UTIME_OMIT};
pub(crate) use linux_raw_sys::general::{O_NOCTTY, O_RDWR};
pub(crate) use linux_raw_sys::general::{XATTR_CREATE, XATTR_REPLACE};
2 changes: 2 additions & 0 deletions src/backend/linux_raw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub(crate) mod net;
))]
pub(crate) mod param;
pub(crate) mod process;
#[cfg(feature = "pty")]
pub(crate) mod pty;
#[cfg(feature = "rand")]
pub(crate) mod rand;
#[cfg(feature = "runtime")]
Expand Down
13 changes: 13 additions & 0 deletions src/backend/linux_raw/process/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use linux_raw_sys::general::{
__kernel_gid_t, __kernel_pid_t, __kernel_uid_t, membarrier_cmd, membarrier_cmd_flag, rlimit,
rlimit64, PRIO_PGRP, PRIO_PROCESS, PRIO_USER, RLIM64_INFINITY, RLIM_INFINITY,
};
use linux_raw_sys::ioctl::TIOCSCTTY;
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "fs")]
use {super::super::conv::ret_c_uint_infallible, crate::fs::Mode};
Expand Down Expand Up @@ -745,3 +746,15 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
let (ptr, len) = slice(name);
unsafe { ret(syscall_readonly!(__NR_sethostname, ptr, len)) }
}

#[inline]
pub(crate) fn ioctl_tiocsctty(fd: BorrowedFd<'_>) -> io::Result<()> {
unsafe {
ret(syscall_readonly!(
__NR_ioctl,
fd,
c_uint(TIOCSCTTY),
by_ref(&0_u32)
))
}
}
1 change: 1 addition & 0 deletions src/backend/linux_raw/pty/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod syscalls;
60 changes: 60 additions & 0 deletions src/backend/linux_raw/pty/syscalls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! linux_raw syscalls supporting `rustix::pty`.
//!
//! # Safety
//!
//! See the `rustix::backend` module documentation for details.
#![allow(unsafe_code)]
#![allow(clippy::undocumented_unsafe_blocks)]

use super::super::c;
use super::super::conv::{by_ref, c_uint, ret, ret_owned_fd};
use crate::fd::{BorrowedFd, OwnedFd};
use crate::ffi::CString;
use crate::io;
use crate::path::DecInt;
use crate::pty::OpenptFlags;
#[cfg(any(apple, freebsdlike, linux_like, target_os = "fuchsia"))]
use alloc::vec::Vec;
use core::mem::MaybeUninit;
use linux_raw_sys::ioctl::{TIOCGPTN, TIOCGPTPEER, TIOCSPTLCK};

#[cfg(any(apple, freebsdlike, linux_like, target_os = "fuchsia"))]
#[inline]
pub(crate) fn ptsname(fd: BorrowedFd, mut buffer: Vec<u8>) -> io::Result<CString> {
unsafe {
let mut n = MaybeUninit::<c::c_int>::uninit();
ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPTN), &mut n))?;

buffer.clear();
buffer.extend_from_slice(b"/dev/pts/");
buffer.extend_from_slice(DecInt::new(n.assume_init()).as_bytes());
// With Rust 1.58 we can append a '\0' ourselves and use
// `from_vec_with_nul_unchecked`.
Ok(CString::from_vec_unchecked(buffer))
}
}

#[inline]
pub(crate) fn unlockpt(fd: BorrowedFd) -> io::Result<()> {
unsafe {
ret(syscall_readonly!(
__NR_ioctl,
fd,
c_uint(TIOCSPTLCK),
by_ref(&0)
))
}
}

#[cfg(target_os = "linux")]
#[inline]
pub(crate) fn ioctl_tiocgptpeer(fd: BorrowedFd, flags: OpenptFlags) -> io::Result<OwnedFd> {
unsafe {
ret_owned_fd(syscall_readonly!(
__NR_ioctl,
fd,
c_uint(TIOCGPTPEER),
c_uint(flags.bits())
))
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ pub mod path;
#[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
pub mod process;
#[cfg(not(windows))]
#[cfg(not(target_os = "wasi"))]
#[cfg(feature = "pty")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "pty")))]
pub mod pty;
#[cfg(not(windows))]
#[cfg(feature = "rand")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
pub mod rand;
Expand Down
21 changes: 21 additions & 0 deletions src/process/ioctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::{backend, io};
use backend::fd::AsFd;

/// `ioctl(fd, TIOCSCTTY, 0)`—Sets the controlling terminal for the processs.
///
/// # References
/// - [Linux]
/// - [FreeBSD]
/// - [NetBSD]
/// - [OpenBSD]
///
/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=tty&sektion=4
/// [NetBSD]: https://man.netbsd.org/tty.4
/// [OpenBSD]: https://man.openbsd.org/tty.4
#[cfg(not(any(windows, target_os = "haiku", target_os = "redox", target_os = "wasi")))]
#[inline]
#[doc(alias = "TIOCSCTTY")]
pub fn ioctl_tiocsctty<Fd: AsFd>(fd: Fd) -> io::Result<()> {
backend::process::syscalls::ioctl_tiocsctty(fd.as_fd())
}
2 changes: 2 additions & 0 deletions src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod chroot;
mod exit;
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
mod id;
mod ioctl;
#[cfg(not(target_os = "wasi"))]
mod kill;
#[cfg(any(target_os = "android", target_os = "linux"))]
Expand Down Expand Up @@ -43,6 +44,7 @@ pub use chroot::*;
pub use exit::*;
#[cfg(not(target_os = "wasi"))]
pub use id::*;
pub use ioctl::*;
#[cfg(not(target_os = "wasi"))]
pub use kill::*;
#[cfg(any(target_os = "android", target_os = "linux"))]
Expand Down
Loading

0 comments on commit c2699e3

Please sign in to comment.