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&section=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