rustix/backend/linux_raw/termios/
syscalls.rs

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::<Winsize>::uninit();
28        ret(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::<Termios>::uninit();
36
37    // SAFETY: This invokes the `TCGETS2` ioctl, which initializes the full
38    // `Termios` structure.
39    unsafe {
40        match ret(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) => 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::<c::pid_t>::uninit();
104        ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGPGRP), &mut result))?;
105        let pid = 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(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(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(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(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(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::<c::pid_t>::uninit();
253        ret(syscall!(__NR_ioctl, fd, c_uint(c::TIOCGSID), &mut result))?;
254        let pid = result.assume_init();
255        Ok(Pid::from_raw_unchecked(pid))
256    }
257}
258
259#[inline]
260pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
261    unsafe {
262        ret(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(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 = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
289
290    debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
291
292    termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD | c::CIBAUD);
293    termios.control_modes |=
294        ControlModes::from_bits_retain(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 = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
307
308    debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
309
310    termios.control_modes -= ControlModes::from_bits_retain(c::CBAUD);
311    termios.control_modes |= ControlModes::from_bits_retain(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 = speed::encode(arbitrary_speed).unwrap_or(c::BOTHER);
323
324    debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
325
326    termios.control_modes -= ControlModes::from_bits_retain(c::CIBAUD);
327    termios.control_modes |= ControlModes::from_bits_retain(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}