1//! linux_raw syscalls supporting `rustix::termios`.
2//!
3//! # Safety
4//!
5//! See the `rustix::backend` module documentation for details.
6#![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
7
8use crate::backend::c;
9use crate::backend::conv::{by_ref, c_uint, ret};
10use crate::fd::BorrowedFd;
11#[cfg(feature = "alloc")]
12use crate::ffi::CStr;
13use crate::io;
14use crate::pid::Pid;
15use crate::termios::{
16 speed, Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes,
17 QueueSelector, SpecialCodeIndex, Termios, Winsize,
18};
19#[cfg(feature = "alloc")]
20#[cfg(feature = "fs")]
21use crate::{fs::FileType, path::DecInt};
22use core::mem::MaybeUninit;
23
24#[inline]
25pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
26 unsafe {
27 let mut result: MaybeUninit = MaybeUninit::<Winsize>::uninit();
28 ret(raw:syscall!(__NR_ioctl, fd, c_uint(c::TIOCGWINSZ), &mut result))?;
29 Ok(result.assume_init())
30 }
31}
32
33#[inline]
34pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
35 let mut result: MaybeUninit = MaybeUninit::<Termios>::uninit();
36
37 // SAFETY: This invokes the `TCGETS2` ioctl, which initializes the full
38 // `Termios` structure.
39 unsafe {
40 match ret(raw:syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result)) {
41 Ok(()) => Ok(result.assume_init()),
42
43 // A `NOTTY` or `ACCESS` might mean the OS doesn't support
44 // `TCGETS2`, for example a seccomp environment or WSL that only
45 // knows about `TCGETS`. Fall back to the old `TCGETS`.
46 #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
47 Err(io::Errno::NOTTY) | Err(io::Errno::ACCESS) => tcgetattr_fallback(fd),
48
49 Err(err: Errno) => Err(err),
50 }
51 }
52}
53
54/// Implement `tcgetattr` using the old `TCGETS` ioctl.
55#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
56#[cold]
57fn tcgetattr_fallback(fd: BorrowedFd<'_>) -> io::Result<Termios> {
58 use core::ptr::{addr_of, addr_of_mut};
59
60 let mut result = MaybeUninit::<Termios>::uninit();
61
62 // SAFETY: This invokes the `TCGETS` ioctl which initializes the `Termios`
63 // structure except for the `input_speed` and `output_speed` fields, which
64 // we manually initialize before forming a reference to the full `Termios`.
65 unsafe {
66 // Do the old `TCGETS` call.
67 ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS), &mut result))?;
68
69 // Read the `control_modes` field without forming a reference to the
70 // `Termios` because it isn't fully initialized yet.
71 let ptr = result.as_mut_ptr();
72 let control_modes = addr_of!((*ptr).control_modes).read();
73
74 // Infer the output speed and set `output_speed`.
75 let encoded_out = control_modes.bits() & c::CBAUD;
76 let output_speed = match speed::decode(encoded_out) {
77 Some(output_speed) => output_speed,
78 None => return Err(io::Errno::RANGE),
79 };
80 addr_of_mut!((*ptr).output_speed).write(output_speed);
81
82 // Infer the input speed and set `input_speed`. `B0` is a special-case
83 // that means the input speed is the same as the output speed.
84 let encoded_in = (control_modes.bits() & c::CIBAUD) >> c::IBSHIFT;
85 let input_speed = if encoded_in == c::B0 {
86 output_speed
87 } else {
88 match speed::decode(encoded_in) {
89 Some(input_speed) => input_speed,
90 None => return Err(io::Errno::RANGE),
91 }
92 };
93 addr_of_mut!((*ptr).input_speed).write(input_speed);
94
95 // Now all the fields are set.
96 Ok(result.assume_init())
97 }
98}
99
100#[inline]
101pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
102 unsafe {
103 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
104 ret(raw:syscall!(__NR_ioctl, fd, c_uint(c::TIOCGPGRP), &mut result))?;
105 let pid: i32 = result.assume_init();
106
107 // This doesn't appear to be documented, but it appears `tcsetpgrp` can
108 // succeed and set the pid to 0 if we pass it a pseudo-terminal device
109 // fd. For now, fail with `OPNOTSUPP`.
110 if pid == 0 {
111 return Err(io::Errno::OPNOTSUPP);
112 }
113
114 Ok(Pid::from_raw_unchecked(raw:pid))
115 }
116}
117
118#[inline]
119pub(crate) fn tcsetattr(
120 fd: BorrowedFd<'_>,
121 optional_actions: OptionalActions,
122 termios: &Termios,
123) -> io::Result<()> {
124 // Translate from `optional_actions` into a `TCSETS2` ioctl request code.
125 // On MIPS, `optional_actions` has `TCSETS` added to it.
126 let request = c::TCSETS2
127 + if cfg!(any(
128 target_arch = "mips",
129 target_arch = "mips32r6",
130 target_arch = "mips64",
131 target_arch = "mips64r6"
132 )) {
133 optional_actions as u32 - c::TCSETS
134 } else {
135 optional_actions as u32
136 };
137
138 // SAFETY: This invokes the `TCSETS2` ioctl.
139 unsafe {
140 match ret(syscall_readonly!(
141 __NR_ioctl,
142 fd,
143 c_uint(request),
144 by_ref(termios)
145 )) {
146 Ok(()) => Ok(()),
147
148 // Similar to `tcgetattr_fallback`, `NOTTY` or `ACCESS` might mean
149 // the OS doesn't support `TCSETS2`. Fall back to the old `TCSETS`.
150 #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
151 Err(io::Errno::NOTTY) | Err(io::Errno::ACCESS) => {
152 tcsetattr_fallback(fd, optional_actions, termios)
153 }
154
155 Err(err) => Err(err),
156 }
157 }
158}
159
160/// Implement `tcsetattr` using the old `TCSETS` ioctl.
161#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
162#[cold]
163fn tcsetattr_fallback(
164 fd: BorrowedFd<'_>,
165 optional_actions: OptionalActions,
166 termios: &Termios,
167) -> io::Result<()> {
168 // `TCSETS` silently accepts `BOTHER` in `c_cflag` even though it doesn't
169 // read `c_ispeed`/`c_ospeed`, so detect this case and fail if needed.
170 let control_modes_bits = termios.control_modes.bits();
171 let encoded_out = control_modes_bits & c::CBAUD;
172 let encoded_in = (control_modes_bits & c::CIBAUD) >> c::IBSHIFT;
173 if encoded_out == c::BOTHER || encoded_in == c::BOTHER {
174 return Err(io::Errno::RANGE);
175 }
176
177 // Translate from `optional_actions` into a `TCSETS` ioctl request code. On
178 // MIPS, `optional_actions` already has `TCSETS` added to it.
179 let request = if cfg!(any(
180 target_arch = "mips",
181 target_arch = "mips32r6",
182 target_arch = "mips64",
183 target_arch = "mips64r6"
184 )) {
185 optional_actions as u32
186 } else {
187 optional_actions as u32 + c::TCSETS
188 };
189
190 // SAFETY: This invokes the `TCSETS` ioctl.
191 unsafe {
192 ret(syscall_readonly!(
193 __NR_ioctl,
194 fd,
195 c_uint(request),
196 by_ref(termios)
197 ))
198 }
199}
200
201#[inline]
202pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
203 unsafe {
204 ret(raw:syscall_readonly!(
205 __NR_ioctl,
206 fd,
207 c_uint(c::TCSBRK),
208 c_uint(0)
209 ))
210 }
211}
212
213#[inline]
214pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
215 unsafe {
216 ret(raw:syscall_readonly!(
217 __NR_ioctl,
218 fd,
219 c_uint(c::TCSBRK),
220 c_uint(1)
221 ))
222 }
223}
224
225#[inline]
226pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
227 unsafe {
228 ret(raw:syscall_readonly!(
229 __NR_ioctl,
230 fd,
231 c_uint(c::TCFLSH),
232 c_uint(queue_selector as u32)
233 ))
234 }
235}
236
237#[inline]
238pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
239 unsafe {
240 ret(raw:syscall_readonly!(
241 __NR_ioctl,
242 fd,
243 c_uint(c::TCXONC),
244 c_uint(action as u32)
245 ))
246 }
247}
248
249#[inline]
250pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
251 unsafe {
252 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
253 ret(raw:syscall!(__NR_ioctl, fd, c_uint(c::TIOCGSID), &mut result))?;
254 let pid: i32 = result.assume_init();
255 Ok(Pid::from_raw_unchecked(raw:pid))
256 }
257}
258
259#[inline]
260pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
261 unsafe {
262 ret(raw:syscall_readonly!(
263 __NR_ioctl,
264 fd,
265 c_uint(c::TIOCSWINSZ),
266 by_ref(&winsize)
267 ))
268 }
269}
270
271#[inline]
272pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
273 let raw_pid: c::c_int = pid.as_raw_nonzero().get();
274 unsafe {
275 ret(raw:syscall_readonly!(
276 __NR_ioctl,
277 fd,
278 c_uint(c::TIOCSPGRP),
279 by_ref(&raw_pid)
280 ))
281 }
282}
283
284/// A wrapper around a conceptual `cfsetspeed` which handles an arbitrary
285/// integer speed value.
286#[inline]
287pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
288 let encoded_speed: u32 = speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
289
290 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
291
292 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD | c::CIBAUD);
293 termios.control_modes |=
294 ControlModes::from_bits_retain(bits:encoded_speed | (encoded_speed << c::IBSHIFT));
295
296 termios.input_speed = arbitrary_speed;
297 termios.output_speed = arbitrary_speed;
298
299 Ok(())
300}
301
302/// A wrapper around a conceptual `cfsetospeed` which handles an arbitrary
303/// integer speed value.
304#[inline]
305pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
306 let encoded_speed: u32 = speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
307
308 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
309
310 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD);
311 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed);
312
313 termios.output_speed = arbitrary_speed;
314
315 Ok(())
316}
317
318/// A wrapper around a conceptual `cfsetispeed` which handles an arbitrary
319/// integer speed value.
320#[inline]
321pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
322 let encoded_speed: u32 = speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
323
324 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
325
326 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CIBAUD);
327 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed << c::IBSHIFT);
328
329 termios.input_speed = arbitrary_speed;
330
331 Ok(())
332}
333
334#[inline]
335pub(crate) fn cfmakeraw(termios: &mut Termios) {
336 // From the Linux [`cfmakeraw` manual page]:
337 //
338 // [`cfmakeraw` manual page]: https://man7.org/linux/man-pages/man3/cfmakeraw.3.html
339 termios.input_modes -= InputModes::IGNBRK
340 | InputModes::BRKINT
341 | InputModes::PARMRK
342 | InputModes::ISTRIP
343 | InputModes::INLCR
344 | InputModes::IGNCR
345 | InputModes::ICRNL
346 | InputModes::IXON;
347 termios.output_modes -= OutputModes::OPOST;
348 termios.local_modes -= LocalModes::ECHO
349 | LocalModes::ECHONL
350 | LocalModes::ICANON
351 | LocalModes::ISIG
352 | LocalModes::IEXTEN;
353 termios.control_modes -= ControlModes::CSIZE | ControlModes::PARENB;
354 termios.control_modes |= ControlModes::CS8;
355
356 // Musl and glibc also do these:
357 termios.special_codes[SpecialCodeIndex::VMIN] = 1;
358 termios.special_codes[SpecialCodeIndex::VTIME] = 0;
359}
360
361#[inline]
362pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
363 // On error, Linux will return either `EINVAL` (2.6.32) or `ENOTTY`
364 // (otherwise), because we assume we're never passing an invalid
365 // file descriptor (which would get `EBADF`). Either way, an error
366 // means we don't have a tty.
367 tcgetwinsize(fd).is_ok()
368}
369
370#[cfg(feature = "alloc")]
371#[cfg(feature = "fs")]
372pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
373 let fd_stat = crate::backend::fs::syscalls::fstat(fd)?;
374
375 // Quick check: if `fd` isn't a character device, it's not a tty.
376 if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice {
377 return Err(io::Errno::NOTTY);
378 }
379
380 // Check that `fd` is really a tty.
381 tcgetwinsize(fd)?;
382
383 // Create the "/proc/self/fd/<fd>" string.
384 let mut proc_self_fd_buf: [u8; 25] = *b"/proc/self/fd/\0\0\0\0\0\0\0\0\0\0\0";
385 let dec_int = DecInt::from_fd(fd);
386 let bytes_with_nul = dec_int.as_bytes_with_nul();
387 proc_self_fd_buf[b"/proc/self/fd/".len()..][..bytes_with_nul.len()]
388 .copy_from_slice(bytes_with_nul);
389
390 // SAFETY: We just wrote a valid C String.
391 let proc_self_fd_path = unsafe { CStr::from_ptr(proc_self_fd_buf.as_ptr().cast()) };
392
393 let ptr = buf.as_mut_ptr();
394 let len = {
395 // Gather the ttyname by reading the "fd" file inside `proc_self_fd`.
396 let (init, uninit) = crate::fs::readlinkat_raw(crate::fs::CWD, proc_self_fd_path, buf)?;
397
398 // If the number of bytes is equal to the buffer length, truncation may
399 // have occurred. This check also ensures that we have enough space for
400 // adding a NUL terminator.
401 if uninit.is_empty() {
402 return Err(io::Errno::RANGE);
403 }
404
405 // `readlinkat` returns the number of bytes placed in the buffer.
406 // NUL-terminate the string at that offset.
407 uninit[0].write(b'\0');
408
409 init.len()
410 };
411
412 // Check that the path we read refers to the same file as `fd`.
413 {
414 // SAFETY: We just wrote the NUL byte above.
415 let path = unsafe { CStr::from_ptr(ptr.cast()) };
416
417 let path_stat = crate::backend::fs::syscalls::stat(path)?;
418 if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino {
419 return Err(io::Errno::NODEV);
420 }
421 }
422
423 // Return the length, excluding the NUL terminator.
424 Ok(len)
425}
426