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