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 Action, ControlModes, InputModes, LocalModes, OptionalActions, OutputModes, QueueSelector,
17 SpecialCodeIndex, Termios, Winsize,
18};
19#[cfg(all(feature = "alloc", feature = "procfs"))]
20use crate::{ffi::CStr, fs::FileType, path::DecInt};
21use core::mem::MaybeUninit;
22use linux_raw_sys::general::IBSHIFT;
23use linux_raw_sys::ioctl::{
24 TCFLSH, TCSBRK, TCXONC, TIOCGPGRP, TIOCGSID, TIOCGWINSZ, TIOCSPGRP, TIOCSWINSZ,
25};
26
27#[inline]
28pub(crate) fn tcgetwinsize(fd: BorrowedFd<'_>) -> io::Result<Winsize> {
29 unsafe {
30 let mut result: MaybeUninit = MaybeUninit::<Winsize>::uninit();
31 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGWINSZ), &mut result))?;
32 Ok(result.assume_init())
33 }
34}
35
36#[inline]
37pub(crate) fn tcgetattr(fd: BorrowedFd<'_>) -> io::Result<Termios> {
38 unsafe {
39 let mut result = MaybeUninit::<Termios>::uninit();
40
41 // QEMU's `TCGETS2` doesn't currently set `input_speed` or
42 // `output_speed` on PowerPC, so zero out the fields ourselves.
43 #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
44 {
45 result.write(core::mem::zeroed());
46 }
47
48 ret(syscall!(__NR_ioctl, fd, c_uint(c::TCGETS2), &mut result))?;
49
50 let result = result.assume_init();
51
52 // QEMU's `TCGETS2` doesn't currently set `input_speed` or
53 // `output_speed` on PowerPC, so set them manually if we can.
54 #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
55 let result = {
56 use crate::termios::speed;
57 let mut result = result;
58 if result.output_speed == 0 && (result.control_modes.bits() & c::CBAUD) != c::BOTHER {
59 if let Some(output_speed) = speed::decode(result.control_modes.bits() & c::CBAUD) {
60 result.output_speed = output_speed;
61 }
62 }
63 if result.input_speed == 0
64 && ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) != c::BOTHER
65 {
66 // For input speeds, `B0` is special-cased to mean the input
67 // speed is the same as the output speed.
68 if ((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT) == c::B0 {
69 result.input_speed = result.output_speed;
70 } else if let Some(input_speed) =
71 speed::decode((result.control_modes.bits() & c::CIBAUD) >> c::IBSHIFT)
72 {
73 result.input_speed = input_speed;
74 }
75 }
76 result
77 };
78
79 Ok(result)
80 }
81}
82
83#[inline]
84pub(crate) fn tcgetpgrp(fd: BorrowedFd<'_>) -> io::Result<Pid> {
85 unsafe {
86 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
87 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGPGRP), &mut result))?;
88 let pid: i32 = result.assume_init();
89 Ok(Pid::from_raw_unchecked(raw:pid))
90 }
91}
92
93#[inline]
94pub(crate) fn tcsetattr(
95 fd: BorrowedFd<'_>,
96 optional_actions: OptionalActions,
97 termios: &Termios,
98) -> io::Result<()> {
99 // Translate from `optional_actions` into an ioctl request code. On MIPS,
100 // `optional_actions` already has `TCGETS` added to it.
101 let request: u32 = linux_raw_sys::ioctl::TCSETS2
102 + if cfg!(any(
103 target_arch = "mips",
104 target_arch = "mips32r6",
105 target_arch = "mips64",
106 target_arch = "mips64r6"
107 )) {
108 optional_actions as u32 - linux_raw_sys::ioctl::TCSETS
109 } else {
110 optional_actions as u32
111 };
112 unsafe {
113 ret(raw:syscall_readonly!(
114 __NR_ioctl,
115 fd,
116 c_uint(request),
117 by_ref(termios)
118 ))
119 }
120}
121
122#[inline]
123pub(crate) fn tcsendbreak(fd: BorrowedFd<'_>) -> io::Result<()> {
124 unsafe { ret(raw:syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(0))) }
125}
126
127#[inline]
128pub(crate) fn tcdrain(fd: BorrowedFd<'_>) -> io::Result<()> {
129 unsafe { ret(raw:syscall_readonly!(__NR_ioctl, fd, c_uint(TCSBRK), c_uint(1))) }
130}
131
132#[inline]
133pub(crate) fn tcflush(fd: BorrowedFd<'_>, queue_selector: QueueSelector) -> io::Result<()> {
134 unsafe {
135 ret(raw:syscall_readonly!(
136 __NR_ioctl,
137 fd,
138 c_uint(TCFLSH),
139 c_uint(queue_selector as u32)
140 ))
141 }
142}
143
144#[inline]
145pub(crate) fn tcflow(fd: BorrowedFd<'_>, action: Action) -> io::Result<()> {
146 unsafe {
147 ret(raw:syscall_readonly!(
148 __NR_ioctl,
149 fd,
150 c_uint(TCXONC),
151 c_uint(action as u32)
152 ))
153 }
154}
155
156#[inline]
157pub(crate) fn tcgetsid(fd: BorrowedFd<'_>) -> io::Result<Pid> {
158 unsafe {
159 let mut result: MaybeUninit = MaybeUninit::<c::pid_t>::uninit();
160 ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCGSID), &mut result))?;
161 let pid: i32 = result.assume_init();
162 Ok(Pid::from_raw_unchecked(raw:pid))
163 }
164}
165
166#[inline]
167pub(crate) fn tcsetwinsize(fd: BorrowedFd<'_>, winsize: Winsize) -> io::Result<()> {
168 unsafe {
169 ret(raw:syscall!(
170 __NR_ioctl,
171 fd,
172 c_uint(TIOCSWINSZ),
173 by_ref(&winsize)
174 ))
175 }
176}
177
178#[inline]
179pub(crate) fn tcsetpgrp(fd: BorrowedFd<'_>, pid: Pid) -> io::Result<()> {
180 unsafe { ret(raw:syscall!(__NR_ioctl, fd, c_uint(TIOCSPGRP), pid)) }
181}
182
183/// A wrapper around a conceptual `cfsetspeed` which handles an arbitrary
184/// integer speed value.
185#[inline]
186pub(crate) fn set_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
187 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
188
189 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
190
191 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD | c::CIBAUD);
192 termios.control_modes |=
193 ControlModes::from_bits_retain(bits:encoded_speed | (encoded_speed << IBSHIFT));
194
195 termios.input_speed = arbitrary_speed;
196 termios.output_speed = arbitrary_speed;
197
198 Ok(())
199}
200
201/// A wrapper around a conceptual `cfsetospeed` which handles an arbitrary
202/// integer speed value.
203#[inline]
204pub(crate) fn set_output_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
205 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
206
207 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
208
209 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CBAUD);
210 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed);
211
212 termios.output_speed = arbitrary_speed;
213
214 Ok(())
215}
216
217/// A wrapper around a conceptual `cfsetispeed` which handles an arbitrary
218/// integer speed value.
219#[inline]
220pub(crate) fn set_input_speed(termios: &mut Termios, arbitrary_speed: u32) -> io::Result<()> {
221 let encoded_speed: u32 = crate::termios::speed::encode(arbitrary_speed).unwrap_or(default:c::BOTHER);
222
223 debug_assert_eq!(encoded_speed & !c::CBAUD, 0);
224
225 termios.control_modes -= ControlModes::from_bits_retain(bits:c::CIBAUD);
226 termios.control_modes |= ControlModes::from_bits_retain(bits:encoded_speed << IBSHIFT);
227
228 termios.input_speed = arbitrary_speed;
229
230 Ok(())
231}
232
233#[inline]
234pub(crate) fn cfmakeraw(termios: &mut Termios) {
235 // From the Linux [`cfmakeraw` manual page]:
236 //
237 // [`cfmakeraw` manual page]: https://man7.org/linux/man-pages/man3/cfmakeraw.3.html
238 termios.input_modes -= InputModes::IGNBRK
239 | InputModes::BRKINT
240 | InputModes::PARMRK
241 | InputModes::ISTRIP
242 | InputModes::INLCR
243 | InputModes::IGNCR
244 | InputModes::ICRNL
245 | InputModes::IXON;
246 termios.output_modes -= OutputModes::OPOST;
247 termios.local_modes -= LocalModes::ECHO
248 | LocalModes::ECHONL
249 | LocalModes::ICANON
250 | LocalModes::ISIG
251 | LocalModes::IEXTEN;
252 termios.control_modes -= ControlModes::CSIZE | ControlModes::PARENB;
253 termios.control_modes |= ControlModes::CS8;
254
255 // Musl and glibc also do these:
256 termios.special_codes[SpecialCodeIndex::VMIN] = 1;
257 termios.special_codes[SpecialCodeIndex::VTIME] = 0;
258}
259
260#[inline]
261pub(crate) fn isatty(fd: BorrowedFd<'_>) -> bool {
262 // On error, Linux will return either `EINVAL` (2.6.32) or `ENOTTY`
263 // (otherwise), because we assume we're never passing an invalid
264 // file descriptor (which would get `EBADF`). Either way, an error
265 // means we don't have a tty.
266 tcgetwinsize(fd).is_ok()
267}
268
269#[cfg(all(feature = "alloc", feature = "procfs"))]
270pub(crate) fn ttyname(fd: BorrowedFd<'_>, buf: &mut [MaybeUninit<u8>]) -> io::Result<usize> {
271 let fd_stat = crate::backend::fs::syscalls::fstat(fd)?;
272
273 // Quick check: if `fd` isn't a character device, it's not a tty.
274 if FileType::from_raw_mode(fd_stat.st_mode) != FileType::CharacterDevice {
275 return Err(io::Errno::NOTTY);
276 }
277
278 // Check that `fd` is really a tty.
279 tcgetwinsize(fd)?;
280
281 // Get a fd to '/proc/self/fd'.
282 let proc_self_fd = procfs::proc_self_fd()?;
283
284 // Gather the ttyname by reading the "fd" file inside `proc_self_fd`.
285 let r = crate::backend::fs::syscalls::readlinkat(
286 proc_self_fd,
287 DecInt::from_fd(fd).as_c_str(),
288 buf,
289 )?;
290
291 // If the number of bytes is equal to the buffer length, truncation may
292 // have occurred. This check also ensures that we have enough space for
293 // adding a NUL terminator.
294 if r == buf.len() {
295 return Err(io::Errno::RANGE);
296 }
297
298 // `readlinkat` returns the number of bytes placed in the buffer.
299 // NUL-terminate the string at that offset.
300 buf[r].write(b'\0');
301
302 // Check that the path we read refers to the same file as `fd`.
303 {
304 // SAFETY: We just wrote the NUL byte above
305 let path = unsafe { CStr::from_ptr(buf.as_ptr().cast()) };
306
307 let path_stat = crate::backend::fs::syscalls::stat(path)?;
308 if path_stat.st_dev != fd_stat.st_dev || path_stat.st_ino != fd_stat.st_ino {
309 return Err(io::Errno::NODEV);
310 }
311 }
312
313 Ok(r)
314}
315