Skip to content

Commit 5fd4792

Browse files
committed
Add a rustix::pty module.
Add a `rustix::pty` module, providing functions that wrap `posix_openpt`, `grantpt`, `unlockpt`, `ptsname`, and so on.
1 parent 94edb27 commit 5fd4792

File tree

17 files changed

+483
-5
lines changed

17 files changed

+483
-5
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ param = ["fs"]
166166
# Enable this to enable `rustix::io::proc_self_*` (on Linux) and `ttyname`.
167167
procfs = ["once_cell", "itoa", "fs"]
168168

169+
# Enable `rustix::pty::*`.
170+
pty = ["itoa", "fs"]
171+
169172
# Enable `rustix::termios::*`.
170173
termios = []
171174

@@ -188,6 +191,7 @@ all-apis = [
188191
"param",
189192
"process",
190193
"procfs",
194+
"pty",
191195
"rand",
192196
"runtime",
193197
"termios",

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ by default. The rest of the API is conditional with cargo feature flags:
6363
| `net` | [`rustix::net`] and [`rustix::path`]—Network-related operations.
6464
| `param` | [`rustix::param`]—Process parameters.
6565
| `process` | [`rustix::process`]—Process-associated operations.
66+
| `pty` | [`rustix::pty`]—Pseduoterminal operations.
6667
| `rand` | [`rustix::rand`]—Random-related operations.
6768
| `termios` | [`rustix::termios`]—Terminal I/O stream operations.
6869
| `thread` | [`rustix::thread`]—Thread-associated operations.
@@ -76,6 +77,7 @@ by default. The rest of the API is conditional with cargo feature flags:
7677
[`rustix::net`]: https://docs.rs/rustix/*/rustix/net/index.html
7778
[`rustix::param`]: https://docs.rs/rustix/*/rustix/param/index.html
7879
[`rustix::process`]: https://docs.rs/rustix/*/rustix/process/index.html
80+
[`rustix::pty`]: https://docs.rs/rustix/*/rustix/pty/index.html
7981
[`rustix::rand`]: https://docs.rs/rustix/*/rustix/rand/index.html
8082
[`rustix::termios`]: https://docs.rs/rustix/*/rustix/termios/index.html
8183
[`rustix::thread`]: https://docs.rs/rustix/*/rustix/thread/index.html

src/backend/libc/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ pub(crate) mod param;
7272
#[cfg(not(windows))]
7373
pub(crate) mod process;
7474
#[cfg(not(windows))]
75+
#[cfg(not(target_os = "wasi"))]
76+
#[cfg(feature = "pty")]
77+
pub(crate) mod pty;
78+
#[cfg(not(windows))]
7579
#[cfg(feature = "rand")]
7680
pub(crate) mod rand;
7781
#[cfg(not(windows))]

src/backend/libc/process/syscalls.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
//! libc syscalls supporting `rustix::process`.
22
33
use super::super::c;
4-
#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
5-
use super::super::conv::borrowed_fd;
6-
use super::super::conv::{c_str, ret, ret_c_int, ret_discarded_char_ptr};
74
#[cfg(not(target_os = "wasi"))]
8-
use super::super::conv::{ret_infallible, ret_pid_t, ret_usize};
5+
use super::super::conv::{borrowed_fd, ret_infallible, ret_pid_t, ret_usize};
6+
use super::super::conv::{c_str, ret, ret_c_int, ret_discarded_char_ptr};
97
#[cfg(any(target_os = "android", target_os = "linux"))]
108
use super::super::conv::{syscall_ret, syscall_ret_u32};
119
#[cfg(any(
@@ -15,7 +13,7 @@ use super::super::conv::{syscall_ret, syscall_ret_u32};
1513
target_os = "linux",
1614
))]
1715
use super::types::RawCpuSet;
18-
#[cfg(not(any(target_os = "wasi", target_os = "fuchsia")))]
16+
#[cfg(not(target_os = "wasi"))]
1917
use crate::fd::BorrowedFd;
2018
#[cfg(target_os = "linux")]
2119
use crate::fd::{AsRawFd, OwnedFd};
@@ -648,3 +646,9 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
648646
))
649647
}
650648
}
649+
650+
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
651+
#[inline]
652+
pub(crate) fn ioctl_tiocsctty(fd: BorrowedFd<'_>) -> io::Result<()> {
653+
unsafe { ret(c::ioctl(borrowed_fd(fd), c::TIOCSCTTY as _, &0_u32)) }
654+
}

src/backend/libc/pty/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod syscalls;

src/backend/libc/pty/syscalls.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! libc syscalls supporting `rustix::pty`.
2+
3+
use super::super::c;
4+
use super::super::conv::{borrowed_fd, ret};
5+
use crate::fd::BorrowedFd;
6+
use crate::io;
7+
#[cfg(target_os = "linux")]
8+
use crate::pty::OpenptFlags;
9+
#[cfg(not(target_os = "android"))]
10+
use {super::super::conv::ret_owned_fd, crate::fd::OwnedFd, crate::pty::OpenptFlags};
11+
#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
12+
use {
13+
crate::ffi::{CStr, CString},
14+
crate::path::SMALL_PATH_BUFFER_SIZE,
15+
};
16+
17+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
18+
#[inline]
19+
pub(crate) fn openpt(flags: OpenptFlags) -> io::Result<OwnedFd> {
20+
unsafe { ret_owned_fd(c::posix_openpt(flags.bits() as _)) }
21+
}
22+
23+
#[cfg(any(apple, linux_like, target_os = "freebsd", target_os = "fuchsia"))]
24+
#[inline]
25+
pub(crate) fn ptsname(fd: BorrowedFd, mut buffer: Vec<u8>) -> io::Result<CString> {
26+
// This code would benefit from having a better way to read into
27+
// uninitialized memory, but that requires `unsafe`.
28+
buffer.clear();
29+
buffer.reserve(SMALL_PATH_BUFFER_SIZE);
30+
buffer.resize(buffer.capacity(), 0_u8);
31+
32+
loop {
33+
// On platforms with `ptsname_r`, use it.
34+
#[cfg(any(target_os = "freebsd", linux_like, target_os = "fuchsia"))]
35+
let r =
36+
unsafe { libc::ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len()) };
37+
38+
// MacOS 10.13.4 has `ptsname_r`; use it if we have it, otherwise fall
39+
// back to calling the underlying ioctl directly.
40+
#[cfg(apple)]
41+
let r = unsafe {
42+
weak! { fn ptsname_r(c::c_int, *mut c::c_char, c::size_t) -> c::c_int }
43+
44+
if let Some(libc_ptsname_r) = ptsname_r.get() {
45+
libc_ptsname_r(borrowed_fd(fd), buffer.as_mut_ptr().cast(), buffer.len())
46+
} else {
47+
// The size declared in the `TIOCPTYGNAME` macro in sys/ttycom.h is 128.
48+
let mut name: [u8; 128] = [0_u8; 128];
49+
match libc::ioctl(borrowed_fd(fd), libc::TIOCPTYGNAME as u64, &mut name) {
50+
0 => {
51+
let len = CStr::from_ptr(name.as_ptr().cast()).to_bytes().len();
52+
std::ptr::copy_nonoverlapping(name.as_ptr(), buffer.as_mut_ptr(), len + 1);
53+
0
54+
}
55+
_ => libc_errno::errno().0,
56+
}
57+
}
58+
};
59+
60+
if r == 0 {
61+
return Ok(unsafe { CStr::from_ptr(buffer.as_ptr().cast()).to_owned() });
62+
}
63+
if r != libc::ERANGE {
64+
return Err(io::Errno::from_raw_os_error(r));
65+
}
66+
67+
buffer.reserve(1); // use `Vec` reallocation strategy to grow capacity exponentially
68+
buffer.resize(buffer.capacity(), 0_u8);
69+
}
70+
}
71+
72+
#[inline]
73+
pub(crate) fn unlockpt(fd: BorrowedFd) -> io::Result<()> {
74+
unsafe { ret(c::unlockpt(borrowed_fd(fd))) }
75+
}
76+
77+
#[cfg(not(any(target_os = "android", target_os = "linux")))]
78+
#[inline]
79+
pub(crate) fn grantpt(fd: BorrowedFd) -> io::Result<()> {
80+
unsafe { ret(c::grantpt(borrowed_fd(fd))) }
81+
}
82+
83+
#[cfg(target_os = "linux")]
84+
#[inline]
85+
pub(crate) fn ioctl_tiocgptpeer(fd: BorrowedFd, flags: OpenptFlags) -> io::Result<OwnedFd> {
86+
unsafe { ret_owned_fd(c::ioctl(borrowed_fd(fd), c::TIOCGPTPEER, flags.bits())) }
87+
}

src/backend/linux_raw/c.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ pub(crate) use linux_raw_sys::general::{
3232
SO_SNDTIMEO_NEW, SO_SNDTIMEO_OLD, SO_TYPE, TCP_NODELAY,
3333
};
3434
pub(crate) use linux_raw_sys::general::{NFS_SUPER_MAGIC, PROC_SUPER_MAGIC, UTIME_NOW, UTIME_OMIT};
35+
pub(crate) use linux_raw_sys::general::{O_NOCTTY, O_RDWR};
3536
pub(crate) use linux_raw_sys::general::{XATTR_CREATE, XATTR_REPLACE};

src/backend/linux_raw/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub(crate) mod net;
4141
))]
4242
pub(crate) mod param;
4343
pub(crate) mod process;
44+
#[cfg(feature = "pty")]
45+
pub(crate) mod pty;
4446
#[cfg(feature = "rand")]
4547
pub(crate) mod rand;
4648
#[cfg(feature = "runtime")]

src/backend/linux_raw/process/syscalls.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use linux_raw_sys::general::{
3131
__kernel_gid_t, __kernel_pid_t, __kernel_uid_t, membarrier_cmd, membarrier_cmd_flag, rlimit,
3232
rlimit64, PRIO_PGRP, PRIO_PROCESS, PRIO_USER, RLIM64_INFINITY, RLIM_INFINITY,
3333
};
34+
use linux_raw_sys::ioctl::TIOCSCTTY;
3435
#[cfg(not(target_os = "wasi"))]
3536
#[cfg(feature = "fs")]
3637
use {super::super::conv::ret_c_uint_infallible, crate::fs::Mode};
@@ -745,3 +746,15 @@ pub(crate) fn sethostname(name: &[u8]) -> io::Result<()> {
745746
let (ptr, len) = slice(name);
746747
unsafe { ret(syscall_readonly!(__NR_sethostname, ptr, len)) }
747748
}
749+
750+
#[inline]
751+
pub(crate) fn ioctl_tiocsctty(fd: BorrowedFd<'_>) -> io::Result<()> {
752+
unsafe {
753+
ret(syscall_readonly!(
754+
__NR_ioctl,
755+
fd,
756+
c_uint(TIOCSCTTY),
757+
by_ref(&0_u32)
758+
))
759+
}
760+
}

src/backend/linux_raw/pty/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod syscalls;

src/backend/linux_raw/pty/syscalls.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! linux_raw syscalls supporting `rustix::pty`.
2+
//!
3+
//! # Safety
4+
//!
5+
//! See the `rustix::backend` module documentation for details.
6+
#![allow(unsafe_code)]
7+
#![allow(clippy::undocumented_unsafe_blocks)]
8+
9+
use super::super::c;
10+
use super::super::conv::{by_ref, c_uint, ret, ret_owned_fd};
11+
use crate::fd::{BorrowedFd, OwnedFd};
12+
use crate::ffi::CString;
13+
use crate::io;
14+
use crate::path::DecInt;
15+
use crate::pty::OpenptFlags;
16+
#[cfg(any(apple, freebsdlike, linux_like, target_os = "fuchsia"))]
17+
use alloc::vec::Vec;
18+
use core::mem::MaybeUninit;
19+
use linux_raw_sys::ioctl::{TIOCGPTN, TIOCGPTPEER, TIOCSPTLCK};
20+
21+
#[cfg(any(apple, freebsdlike, linux_like, target_os = "fuchsia"))]
22+
#[inline]
23+
pub(crate) fn ptsname(fd: BorrowedFd, mut buffer: Vec<u8>) -> io::Result<CString> {
24+
unsafe {
25+
let mut n = MaybeUninit::<c::c_int>::uninit();
26+
ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPTN), &mut n))?;
27+
28+
buffer.clear();
29+
buffer.extend_from_slice(b"/dev/pts/");
30+
buffer.extend_from_slice(DecInt::new(n.assume_init()).as_bytes());
31+
// With Rust 1.58 we can append a '\0' ourselves and use
32+
// `from_vec_with_nul_unchecked`.
33+
Ok(CString::from_vec_unchecked(buffer))
34+
}
35+
}
36+
37+
#[inline]
38+
pub(crate) fn unlockpt(fd: BorrowedFd) -> io::Result<()> {
39+
unsafe {
40+
ret(syscall_readonly!(
41+
__NR_ioctl,
42+
fd,
43+
c_uint(TIOCSPTLCK),
44+
by_ref(&0)
45+
))
46+
}
47+
}
48+
49+
#[cfg(target_os = "linux")]
50+
#[inline]
51+
pub(crate) fn ioctl_tiocgptpeer(fd: BorrowedFd, flags: OpenptFlags) -> io::Result<OwnedFd> {
52+
unsafe {
53+
ret_owned_fd(syscall_readonly!(
54+
__NR_ioctl,
55+
fd,
56+
c_uint(TIOCGPTPEER),
57+
c_uint(flags.bits())
58+
))
59+
}
60+
}

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ pub mod path;
200200
#[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
201201
pub mod process;
202202
#[cfg(not(windows))]
203+
#[cfg(not(target_os = "wasi"))]
204+
#[cfg(feature = "pty")]
205+
#[cfg_attr(doc_cfg, doc(cfg(feature = "pty")))]
206+
pub mod pty;
207+
#[cfg(not(windows))]
203208
#[cfg(feature = "rand")]
204209
#[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
205210
pub mod rand;

src/process/ioctl.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use crate::{backend, io};
2+
use backend::fd::AsFd;
3+
4+
/// `ioctl(fd, TIOCSCTTY, 0)`—Sets the controlling terminal for the processs.
5+
///
6+
/// # References
7+
/// - [Linux]
8+
/// - [FreeBSD]
9+
/// - [NetBSD]
10+
/// - [OpenBSD]
11+
///
12+
/// [Linux]: https://man7.org/linux/man-pages/man4/tty_ioctl.4.html
13+
/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=tty&sektion=4
14+
/// [NetBSD]: https://man.netbsd.org/tty.4
15+
/// [OpenBSD]: https://man.openbsd.org/tty.4
16+
#[cfg(not(any(windows, target_os = "haiku", target_os = "redox", target_os = "wasi")))]
17+
#[inline]
18+
#[doc(alias = "TIOCSCTTY")]
19+
pub fn ioctl_tiocsctty<Fd: AsFd>(fd: Fd) -> io::Result<()> {
20+
backend::process::syscalls::ioctl_tiocsctty(fd.as_fd())
21+
}

src/process/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod chroot;
77
mod exit;
88
#[cfg(not(target_os = "wasi"))] // WASI doesn't have get[gpu]id.
99
mod id;
10+
mod ioctl;
1011
#[cfg(not(target_os = "wasi"))]
1112
mod kill;
1213
#[cfg(any(target_os = "android", target_os = "linux"))]
@@ -43,6 +44,7 @@ pub use chroot::*;
4344
pub use exit::*;
4445
#[cfg(not(target_os = "wasi"))]
4546
pub use id::*;
47+
pub use ioctl::*;
4648
#[cfg(not(target_os = "wasi"))]
4749
pub use kill::*;
4850
#[cfg(any(target_os = "android", target_os = "linux"))]

0 commit comments

Comments
 (0)