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