rustix/pty.rs
1//! Pseudoterminal operations.
2//!
3//! For the `openpty` and `login_tty` functions, see the
4//! [rustix-openpty crate].
5//!
6//! [rustix-openpty crate]: https://crates.io/crates/rustix-openpty
7
8#![allow(unsafe_code)]
9
10use crate::backend::c;
11use crate::fd::{AsFd, OwnedFd};
12use crate::fs::OFlags;
13use crate::{backend, io};
14#[cfg(all(
15 feature = "alloc",
16 any(
17 apple,
18 linux_like,
19 target_os = "freebsd",
20 target_os = "fuchsia",
21 target_os = "illumos"
22 )
23))]
24use {crate::ffi::CString, alloc::vec::Vec};
25
26#[cfg(target_os = "linux")]
27use crate::{fd::FromRawFd as _, ioctl};
28
29bitflags::bitflags! {
30 /// `O_*` flags for use with [`openpt`] and [`ioctl_tiocgptpeer`].
31 #[repr(transparent)]
32 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
33 pub struct OpenptFlags: u32 {
34 /// `O_RDWR`
35 const RDWR = c::O_RDWR as c::c_uint;
36
37 /// `O_NOCTTY`
38 #[cfg(not(any(target_os = "espidf", target_os = "horizon", target_os = "l4re", target_os = "redox", target_os = "vita")))]
39 const NOCTTY = c::O_NOCTTY as c::c_uint;
40
41 /// `O_CLOEXEC`
42 ///
43 /// The standard `posix_openpt` function doesn't support `CLOEXEC`, but
44 /// rustix supports it on Linux, and FreeBSD and NetBSD support it.
45 #[cfg(any(linux_kernel, target_os = "freebsd", target_os = "netbsd"))]
46 const CLOEXEC = c::O_CLOEXEC as c::c_uint;
47
48 /// <https://docs.rs/bitflags/*/bitflags/#externally-defined-flags>
49 const _ = !0;
50 }
51}
52
53impl From<OpenptFlags> for OFlags {
54 #[inline]
55 fn from(flags: OpenptFlags) -> Self {
56 // `OpenptFlags` is a subset of `OFlags`.
57 Self::from_bits_retain(flags.bits() as _)
58 }
59}
60
61/// `posix_openpt(flags)`—Open a pseudoterminal device.
62///
63/// On Linux, an additional `CLOEXEC` flag value may be passed to request the
64/// close-on-exec flag be set.
65///
66/// On Linux, if the system has no free pseudoterminals available, the
67/// underlying system call fails with [`io::Errno::NOSPC`], however this rustix
68/// function translates that to [`io::Errno::AGAIN`], so that the linux_raw and
69/// libc backends have the same behavior.
70///
71/// # References
72/// - [POSIX]
73/// - [Linux]
74/// - [Apple]
75/// - [FreeBSD]
76/// - [DragonFly BSD]
77/// - [NetBSD]
78/// - [OpenBSD]
79/// - [illumos]
80///
81/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_openpt.html
82/// [Linux]: https://man7.org/linux/man-pages/man3/posix_openpt.3.html
83/// [Apple]: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/posix_openpt.3.html
84/// [FreeBSD]: https://man.freebsd.org/cgi/man.cgi?query=posix_openpt&sektion=2
85/// [DragonFly BSD]: https://man.dragonflybsd.org/?command=posix_openpt§ion=3
86/// [NetBSD]: https://man.netbsd.org/posix_openpt.3
87/// [OpenBSD]: https://man.openbsd.org/posix_openpt
88/// [illumos]: https://illumos.org/man/3C/posix_openpt
89#[inline]
90#[doc(alias = "posix_openpt")]
91pub fn openpt(flags: OpenptFlags) -> io::Result<OwnedFd> {
92 // On Linux, open the device ourselves so that we can support `CLOEXEC`.
93 #[cfg(linux_kernel)]
94 {
95 use crate::fs::{open, Mode};
96 match open(cstr!("/dev/ptmx"), flags.into(), Mode::empty()) {
97 // Match libc `openat` behavior with `ENOSPC`.
98 Err(io::Errno::NOSPC) => Err(io::Errno::AGAIN),
99 otherwise => otherwise,
100 }
101 }
102
103 // On all other platforms, use `openpt`.
104 #[cfg(not(linux_kernel))]
105 {
106 backend::pty::syscalls::openpt(flags)
107 }
108}
109
110/// `ptsname(fd)`—Return the name of a pseudoterminal.
111///
112/// # References
113/// - [POSIX]
114/// - [Linux]
115/// - [illumos]
116/// - [glibc]
117///
118/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/ptsname.html
119/// [Linux]: https://man7.org/linux/man-pages/man3/ptsname.3.html
120/// [illumos]: https://www.illumos.org/man/3C/ptsname
121/// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Allocation.html#index-ptsname
122#[cfg(all(
123 feature = "alloc",
124 any(
125 apple,
126 linux_like,
127 target_os = "freebsd",
128 target_os = "fuchsia",
129 target_os = "illumos"
130 )
131))]
132#[inline]
133#[doc(alias = "ptsname_r")]
134#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
135pub fn ptsname<Fd: AsFd, B: Into<Vec<u8>>>(fd: Fd, reuse: B) -> io::Result<CString> {
136 backend::pty::syscalls::ptsname(fd.as_fd(), reuse.into())
137}
138
139/// `unlockpt(fd)`—Unlock a pseudoterminal.
140///
141/// # References
142/// - [POSIX]
143/// - [Linux]
144/// - [glibc]
145///
146/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/unlockpt.html
147/// [Linux]: https://man7.org/linux/man-pages/man3/unlockpt.3.html
148/// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Allocation.html#index-unlockpt
149#[inline]
150pub fn unlockpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
151 backend::pty::syscalls::unlockpt(fd.as_fd())
152}
153
154/// `grantpt(fd)`—Grant access to the user side of a pseudoterminal.
155///
156/// On Linux, calling this function has no effect, as the kernel is expected to
157/// grant the appropriate access. On all other platforms, this function has
158/// unspecified behavior if the calling process has a [`Signal::CHILD`] signal
159/// handler installed.
160///
161/// # References
162/// - [POSIX]
163/// - [Linux]
164/// - [glibc]
165///
166/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/grantpt.html
167/// [Linux]: https://man7.org/linux/man-pages/man3/grantpt.3.html
168/// [glibc]: https://sourceware.org/glibc/manual/latest/html_node/Allocation.html#index-grantpt
169/// [`Signal::CHILD`]: crate::process::Signal::CHILD
170#[inline]
171pub fn grantpt<Fd: AsFd>(fd: Fd) -> io::Result<()> {
172 #[cfg(not(linux_kernel))]
173 {
174 backend::pty::syscalls::grantpt(fd.as_fd())
175 }
176
177 // On Linux, we assume the kernel has already granted the needed
178 // permissions to the user side of the pseudoterminal.
179 #[cfg(linux_kernel)]
180 {
181 let _ = fd;
182 Ok(())
183 }
184}
185
186/// `ioctl(fd, TIOCGPTPEER)`—Open the user side of a pseudoterminal.
187///
188/// This function is currently only implemented on Linux.
189///
190/// # References
191/// - [Linux]
192///
193/// [Linux]: https://man7.org/linux/man-pages/man2/ioctl_tty.2.html
194#[cfg(target_os = "linux")]
195#[inline]
196pub fn ioctl_tiocgptpeer<Fd: AsFd>(fd: Fd, flags: OpenptFlags) -> io::Result<OwnedFd> {
197 unsafe { ioctl::ioctl(fd, Tiocgptpeer(flags)) }
198}
199
200#[cfg(target_os = "linux")]
201struct Tiocgptpeer(OpenptFlags);
202
203#[cfg(target_os = "linux")]
204unsafe impl ioctl::Ioctl for Tiocgptpeer {
205 type Output = OwnedFd;
206
207 const IS_MUTATING: bool = false;
208
209 fn opcode(&self) -> ioctl::Opcode {
210 c::TIOCGPTPEER as ioctl::Opcode
211 }
212
213 fn as_ptr(&mut self) -> *mut c::c_void {
214 self.0.bits() as *mut c::c_void
215 }
216
217 unsafe fn output_from_ptr(
218 ret: ioctl::IoctlOutput,
219 _arg: *mut c::c_void,
220 ) -> io::Result<Self::Output> {
221 Ok(OwnedFd::from_raw_fd(ret))
222 }
223}