]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! linux_raw syscalls supporting `rustix::termios`. |
2 | //! | |
3 | //! # Safety | |
4 | //! | |
487cf647 | 5 | //! See the `rustix::backend` module documentation for details. |
064997fb FG |
6 | #![allow(unsafe_code)] |
7 | #![allow(clippy::undocumented_unsafe_blocks)] | |
8 | ||
9 | use super::super::conv::{by_ref, c_uint, ret}; | |
10 | use crate::fd::BorrowedFd; | |
11 | use crate::io; | |
12 | use crate::process::{Pid, RawNonZeroPid}; | |
13 | use crate::termios::{ | |
14 | Action, OptionalActions, QueueSelector, Termios, Winsize, BRKINT, CBAUD, CS8, CSIZE, ECHO, | |
15 | ECHONL, ICANON, ICRNL, IEXTEN, IGNBRK, IGNCR, INLCR, ISIG, ISTRIP, IXON, OPOST, PARENB, PARMRK, | |
16 | VMIN, VTIME, | |
17 | }; | |
18 | #[cfg(feature = "procfs")] | |
19 | use crate::{ffi::CStr, fs::FileType, path::DecInt}; | |
20 | use core::mem::MaybeUninit; | |
21 | use linux_raw_sys::general::__kernel_pid_t; | |
22 | use linux_raw_sys::ioctl::{ | |
23 | TCFLSH, TCGETS, TCSBRK, TCSETS, TCXONC, TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCSPGRP, TIOCSWINSZ, | |
24 | }; | |
25 | ||
26 | #[inline] | |
27 | pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> { | |
28 | unsafe { | |
29 | let mut result = MaybeUninit::<Winsize>::uninit(); | |
487cf647 FG |
30 | ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGWINSZ), &mut result))?; |
31 | Ok(result.assume_init()) | |
064997fb FG |
32 | } |
33 | } | |
34 | ||
35 | #[inline] | |
36 | pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> { | |
37 | unsafe { | |
38 | let mut result = MaybeUninit::<Termios>::uninit(); | |
487cf647 FG |
39 | ret(syscall!(__NR_ioctl, fd, c_uint(TCGETS), &mut result))?; |
40 | Ok(result.assume_init()) | |
064997fb FG |
41 | } |
42 | } | |
43 | ||
353b0b11 FG |
44 | #[inline] |
45 | #[cfg(any( | |
46 | target_arch = "x86", | |
47 | target_arch = "x86_64", | |
48 | target_arch = "x32", | |
49 | target_arch = "riscv64", | |
50 | target_arch = "aarch64", | |
51 | target_arch = "arm", | |
52 | target_arch = "mips", | |
53 | target_arch = "mips64", | |
54 | ))] | |
55 | pub(crate) fn tcgetattr2(fd: BorrowedFd<'_>) -> io::Result<crate::termios::Termios2> { | |
56 | unsafe { | |
57 | let mut result = MaybeUninit::<crate::termios::Termios2>::uninit(); | |
58 | ret(syscall!( | |
59 | __NR_ioctl, | |
60 | fd, | |
61 | c_uint(linux_raw_sys::ioctl::TCGETS2), | |
62 | &mut result | |
63 | ))?; | |
64 | Ok(result.assume_init()) | |
65 | } | |
66 | } | |
67 | ||
064997fb FG |
68 | #[inline] |
69 | pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> { | |
70 | unsafe { | |
71 | let mut result = MaybeUninit::<__kernel_pid_t>::uninit(); | |
487cf647 FG |
72 | ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGPGRP), &mut result))?; |
73 | let pid = result.assume_init(); | |
74 | debug_assert!(pid > 0); | |
75 | Ok(Pid::from_raw_nonzero(RawNonZeroPid::new_unchecked( | |
76 | pid as u32, | |
77 | ))) | |
064997fb FG |
78 | } |
79 | } | |
80 | ||
81 | #[inline] | |
82 | pub(crate) fn tcsetattr( | |
83 | fd: BorrowedFd, | |
84 | optional_actions: OptionalActions, | |
85 | termios: &Termios, | |
86 | ) -> io::Result<()> { | |
87 | // Translate from `optional_actions` into an ioctl request code. On MIPS, | |
88 | // `optional_actions` already has `TCGETS` added to it. | |
89 | let request = if cfg!(any(target_arch = "mips", target_arch = "mips64")) { | |
90 | optional_actions as u32 | |
91 | } else { | |
92 | TCSETS + optional_actions as u32 | |
93 | }; | |
94 | unsafe { | |
95 | ret(syscall_readonly!( | |
96 | __NR_ioctl, | |
97 | fd, | |
98 | c_uint(request as u32), | |
99 | by_ref(termios) | |
100 | )) | |
101 | } | |
102 | } | |
103 | ||
353b0b11 FG |
104 | #[inline] |
105 | #[cfg(any( | |
106 | target_arch = "x86", | |
107 | target_arch = "x86_64", | |
108 | target_arch = "x32", | |
109 | target_arch = "riscv64", | |
110 | target_arch = "aarch64", | |
111 | target_arch = "arm", | |
112 | target_arch = "mips", | |
113 | target_arch = "mips64", | |
114 | ))] | |
115 | pub(crate) fn tcsetattr2( | |
116 | fd: BorrowedFd, | |
117 | optional_actions: OptionalActions, | |
118 | termios: &crate::termios::Termios2, | |
119 | ) -> io::Result<()> { | |
120 | unsafe { | |
121 | ret(syscall_readonly!( | |
122 | __NR_ioctl, | |
123 | fd, | |
124 | c_uint(linux_raw_sys::ioctl::TCSETS2 + optional_actions as u32), | |
125 | by_ref(termios) | |
126 | )) | |
127 | } | |
128 | } | |
129 | ||
064997fb FG |
130 | #[inline] |
131 | pub(crate) fn tcsendbreak(fd: BorrowedFd) -> io::Result<()> { | |
132 | unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(0))) } | |
133 | } | |
134 | ||
135 | #[inline] | |
136 | pub(crate) fn tcdrain(fd: BorrowedFd) -> io::Result<()> { | |
137 | unsafe { ret(syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(1))) } | |
138 | } | |
139 | ||
140 | #[inline] | |
141 | pub(crate) fn tcflush(fd: BorrowedFd, queue_selector: QueueSelector) -> io::Result<()> { | |
142 | unsafe { | |
143 | ret(syscall_readonly!( | |
144 | __NR_ioctl, | |
145 | fd, | |
146 | c_uint(TCFLSH), | |
147 | c_uint(queue_selector as u32) | |
148 | )) | |
149 | } | |
150 | } | |
151 | ||
152 | #[inline] | |
153 | pub(crate) fn tcflow(fd: BorrowedFd, action: Action) -> io::Result<()> { | |
154 | unsafe { | |
155 | ret(syscall_readonly!( | |
156 | __NR_ioctl, | |
157 | fd, | |
158 | c_uint(TCXONC), | |
159 | c_uint(action as u32) | |
160 | )) | |
161 | } | |
162 | } | |
163 | ||
164 | #[inline] | |
165 | pub(crate) fn tcgetsid(fd: BorrowedFd) -> io::Result<Pid> { | |
166 | unsafe { | |
167 | let mut result = MaybeUninit::<__kernel_pid_t>::uninit(); | |
487cf647 FG |
168 | ret(syscall!(__NR_ioctl, fd, c_uint(TIOCGSID), &mut result))?; |
169 | let pid = result.assume_init(); | |
170 | debug_assert!(pid > 0); | |
171 | Ok(Pid::from_raw_nonzero(RawNonZeroPid::new_unchecked( | |
172 | pid as u32, | |
173 | ))) | |
064997fb FG |
174 | } |
175 | } | |
176 | ||
177 | #[inline] | |
178 | pub(crate) fn tcsetwinsize(fd: BorrowedFd, winsize: Winsize) -> io::Result<()> { | |
179 | unsafe { | |
180 | ret(syscall!( | |
181 | __NR_ioctl, | |
182 | fd, | |
183 | c_uint(TIOCSWINSZ), | |
184 | by_ref(&winsize) | |
185 | )) | |
186 | } | |
187 | } | |
188 | ||
189 | #[inline] | |
190 | pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> { | |
191 | unsafe { ret(syscall!(__NR_ioctl, fd, c_uint(TIOCSPGRP), pid)) } | |
192 | } | |
193 | ||
194 | #[inline] | |
195 | #[must_use] | |
196 | #[allow(clippy::missing_const_for_fn)] | |
197 | pub(crate) fn cfgetospeed(termios: &Termios) -> u32 { | |
198 | termios.c_cflag & CBAUD | |
199 | } | |
200 | ||
201 | #[inline] | |
202 | #[must_use] | |
203 | #[allow(clippy::missing_const_for_fn)] | |
204 | pub(crate) fn cfgetispeed(termios: &Termios) -> u32 { | |
205 | termios.c_cflag & CBAUD | |
206 | } | |
207 | ||
208 | #[inline] | |
209 | pub(crate) fn cfmakeraw(termios: &mut Termios) { | |
210 | // From the Linux [`cfmakeraw` man page]: | |
211 | // | |
212 | // [`cfmakeraw` man page]: https://man7.org/linux/man-pages/man3/cfmakeraw.3.html | |
213 | termios.c_iflag &= !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); | |
214 | termios.c_oflag &= !OPOST; | |
215 | termios.c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN); | |
216 | termios.c_cflag &= !(CSIZE | PARENB); | |
217 | termios.c_cflag |= CS8; | |
218 | ||
219 | // Musl and glibc also do these: | |
220 | termios.c_cc[VMIN] = 1; | |
221 | termios.c_cc[VTIME] = 0; | |
222 | } | |
223 | ||
224 | #[inline] | |
225 | pub(crate) fn cfsetospeed(termios: &mut Termios, speed: u32) -> io::Result<()> { | |
226 | if (speed & !CBAUD) != 0 { | |
227 | return Err(io::Errno::INVAL); | |
228 | } | |
229 | termios.c_cflag &= !CBAUD; | |
230 | termios.c_cflag |= speed; | |
231 | Ok(()) | |
232 | } | |
233 | ||
234 | #[inline] | |
235 | pub(crate) fn cfsetispeed(termios: &mut Termios, speed: u32) -> io::Result<()> { | |
236 | if speed == 0 { | |
237 | return Ok(()); | |
238 | } | |
239 | if (speed & !CBAUD) != 0 { | |
240 | return Err(io::Errno::INVAL); | |
241 | } | |
242 | termios.c_cflag &= !CBAUD; | |
243 | termios.c_cflag |= speed; | |
244 | Ok(()) | |
245 | } | |
246 | ||
247 | #[inline] | |
248 | pub(crate) fn cfsetspeed(termios: &mut Termios, speed: u32) -> io::Result<()> { | |
249 | if (speed & !CBAUD) != 0 { | |
250 | return Err(io::Errno::INVAL); | |
251 | } | |
252 | termios.c_cflag &= !CBAUD; | |
253 | termios.c_cflag |= speed; | |
254 | Ok(()) | |
255 | } | |
256 | ||
257 | #[inline] | |
258 | pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool { | |
259 | // On error, Linux will return either `EINVAL` (2.6.32) or `ENOTTY` | |
260 | // (otherwise), because we assume we're never passing an invalid | |
261 | // file descriptor (which would get `EBADF`). Either way, an error | |
262 | // means we don't have a tty. | |
263 | tcgetwinsize(fd).is_ok() | |
264 | } | |
265 | ||
266 | #[cfg(feature = "procfs")] | |
267 | pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [u8]) -> io::Result<usize> { | |
268 | let fd_stat = super::super::fs::syscalls::fstat(fd)?; | |
269 | ||
270 | // Quick check: if `fd` isn't a character device, it's not a tty. | |
271 | if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice { | |
272 | return Err(crate::io::Errno::NOTTY); | |
273 | } | |
274 | ||
275 | // Check that `fd` is really a tty. | |
276 | tcgetwinsize(fd)?; | |
277 | ||
278 | // Get a fd to '/proc/self/fd'. | |
279 | let proc_self_fd = io::proc_self_fd()?; | |
280 | ||
281 | // Gather the ttyname by reading the 'fd' file inside 'proc_self_fd'. | |
282 | let r = | |
487cf647 | 283 | super::super::fs::syscalls::readlinkat(proc_self_fd, DecInt::from_fd(fd).as_c_str(), buf)?; |
064997fb FG |
284 | |
285 | // If the number of bytes is equal to the buffer length, truncation may | |
286 | // have occurred. This check also ensures that we have enough space for | |
287 | // adding a NUL terminator. | |
288 | if r == buf.len() { | |
289 | return Err(io::Errno::RANGE); | |
290 | } | |
291 | buf[r] = b'\0'; | |
292 | ||
293 | // Check that the path we read refers to the same file as `fd`. | |
294 | let path = CStr::from_bytes_with_nul(&buf[..=r]).unwrap(); | |
295 | ||
296 | let path_stat = super::super::fs::syscalls::stat(path)?; | |
297 | if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino { | |
298 | return Err(crate::io::Errno::NODEV); | |
299 | } | |
300 | ||
301 | Ok(r) | |
302 | } |