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